diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 00000000..e3693ed2 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,23 @@ +name: check +run-name: branch check +on: [pull_request] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16' + - run: npm ci + - run: npm run lint + + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16' + - run: npm ci + - run: npm run test diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..230e0d8b --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,28 @@ +on: + push: + tags: + - v* + +permissions: + contents: write + pull-requests: write + +name: release-please + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/release-please-action@v3 + with: + release-type: node + package-name: '@xsolla/pay-station-sdk' + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 16 + registry-url: 'https://registry.npmjs.org' + - run: npm ci + - run: npm run lib-publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.gitignore b/.gitignore index 6b14d77b..b67bfa44 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,9 @@ node_modules .editorconfig .npmrc .idea +.vscode +dist +lib + +# misc +coverage diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 00000000..c160a771 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx --no -- commitlint --edit ${1} diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..d5a15960 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.10.0 diff --git a/.prettierrc.json b/.prettierrc.json index 937375d2..57cbbbac 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,4 +1,5 @@ { "semi": true, - "singleQuote": true + "singleQuote": true, + "bracketSpacing": true } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..196fa56b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,63 @@ +# Changelog + +## [0.0.3](https://github.com/xsolla/pay-station-sdk/compare/v0.0.2...v0.0.3) (2023-10-12) + +### Features + +- add saved methods, user balance ([2f15d74](https://github.com/xsolla/pay-station-sdk/commit/2f15d7423f4161a2f9a26d3c323b73f1a39c2a41)) +- added getStatus form method ([e2da1ac](https://github.com/xsolla/pay-station-sdk/commit/e2da1ace995ea04bf73748b970bb2dcb7c75565b)) +- added onNextAction form method ([723699b](https://github.com/xsolla/pay-station-sdk/commit/723699b818c3004bca2c82134464f418c7892d0d)) +- added status component ([ef04c96](https://github.com/xsolla/pay-station-sdk/commit/ef04c9631644441c5de39fdd77e76cb93487d3e2)) +- error handler ([6222745](https://github.com/xsolla/pay-station-sdk/commit/62227455bfdbbdb138473e4ef8ee1959b64fd4c4)) +- implement i18n ([c58aae6](https://github.com/xsolla/pay-station-sdk/commit/c58aae642f46c3c5bd2ab641fa18341a6f42a9ba)) +- **PAYMENTS-14328:** add payment-methods component ([c9088d3](https://github.com/xsolla/pay-station-sdk/commit/c9088d3636cc4ce4538e321f7bbc444f5289bddc)) +- **PAYMENTS-14329:** add readme and PaymentMethods integration example ([66595e7](https://github.com/xsolla/pay-station-sdk/commit/66595e7842f27981dd357e89dc7e667483e7a18a)) +- **PAYMENTS-15236:** add finance details component ([9f9708a](https://github.com/xsolla/pay-station-sdk/commit/9f9708a6004f40e8a022f8aa75e4d89c97e6307a)) +- **PAYMENTS-15242:** add form initialization ([583db7c](https://github.com/xsolla/pay-station-sdk/commit/583db7c21768d6e9ae44c21ea5314af53e6b424c)) +- **PAYMENTS-15244:** add payment-form component ([2d154ad](https://github.com/xsolla/pay-station-sdk/commit/2d154addce263e13d2e03948b934539d9eff6a62)) +- **PAYMENTS-15245:** add legal component ([b295418](https://github.com/xsolla/pay-station-sdk/commit/b2954181b92b8a02f2d466ee8a446a6fa336e37c)) +- **PAYMENTS-15256:** set secure component styles ([b08aaf3](https://github.com/xsolla/pay-station-sdk/commit/b08aaf3f1f6564ab426270dc861c694e7756f790)) +- **PAYMENTS-15270:** form - activate & getStatus ([23d28ef](https://github.com/xsolla/pay-station-sdk/commit/23d28ef1b2c9c6ea4d96292b7c068c02ad376bd3)) +- **PAYMENTS-15270:** form - activate & getStatus + tests ([edf95d7](https://github.com/xsolla/pay-station-sdk/commit/edf95d7cc7bf466b827354710903450306fbd2d4)) +- **PAYMENTS-15271:** show errors show field state actions ([1b31e0e](https://github.com/xsolla/pay-station-sdk/commit/1b31e0eafd9f3a4910a0502440f43473bedc614c)) +- **PAYMENTS-16028:** get status without form init ([90fa9c9](https://github.com/xsolla/pay-station-sdk/commit/90fa9c9188e66a0a48d7bc1daa6394bf28229322)) +- **PAYMENTS-16240:** listen control validation ([0fe5a77](https://github.com/xsolla/pay-station-sdk/commit/0fe5a77ceb79522bf46e77105e20bf50e6a99ce3)) +- **TextComponent:** show validation errors ([5e53dea](https://github.com/xsolla/pay-station-sdk/commit/5e53dea3ed364831aac7f04411af679af0aec307)) +- web components architecture implementation ([65b260e](https://github.com/xsolla/pay-station-sdk/commit/65b260ee12683f4a872390fb00c218ea4856a6ca)) + +### Bug Fixes + +- added legal disclaimer translation ([1d0240e](https://github.com/xsolla/pay-station-sdk/commit/1d0240ec73cca6570e53e7d96ea849cd53355ec2)) +- legal check ping messages ([c476097](https://github.com/xsolla/pay-station-sdk/commit/c476097723324a09446611cdb195283d58ec087c)) +- **PAYMENTS-15239:** change text component selector ([f1cbbe9](https://github.com/xsolla/pay-station-sdk/commit/f1cbbe9eb976dabace057e45baadde1442016d1d)) +- **PAYMENTS-15239:** emit selected method id on li element click ([d113dc8](https://github.com/xsolla/pay-station-sdk/commit/d113dc80352967214287e47094217e2626c12f85)) + +## [0.0.2](https://github.com/xsolla/pay-station-sdk/compare/v0.0.1...v0.0.2) (2023-09-22) + +### Features + +- add saved methods, user balance ([2f15d74](https://github.com/xsolla/pay-station-sdk/commit/2f15d7423f4161a2f9a26d3c323b73f1a39c2a41)) +- added getStatus form method ([e2da1ac](https://github.com/xsolla/pay-station-sdk/commit/e2da1ace995ea04bf73748b970bb2dcb7c75565b)) +- added onNextAction form method ([723699b](https://github.com/xsolla/pay-station-sdk/commit/723699b818c3004bca2c82134464f418c7892d0d)) +- added status component ([ef04c96](https://github.com/xsolla/pay-station-sdk/commit/ef04c9631644441c5de39fdd77e76cb93487d3e2)) +- error handler ([6222745](https://github.com/xsolla/pay-station-sdk/commit/62227455bfdbbdb138473e4ef8ee1959b64fd4c4)) +- implement i18n ([c58aae6](https://github.com/xsolla/pay-station-sdk/commit/c58aae642f46c3c5bd2ab641fa18341a6f42a9ba)) +- **PAYMENTS-14328:** add payment-methods component ([c9088d3](https://github.com/xsolla/pay-station-sdk/commit/c9088d3636cc4ce4538e321f7bbc444f5289bddc)) +- **PAYMENTS-14329:** add readme and PaymentMethods integration example ([66595e7](https://github.com/xsolla/pay-station-sdk/commit/66595e7842f27981dd357e89dc7e667483e7a18a)) +- **PAYMENTS-15236:** add finance details component ([9f9708a](https://github.com/xsolla/pay-station-sdk/commit/9f9708a6004f40e8a022f8aa75e4d89c97e6307a)) +- **PAYMENTS-15242:** add form initialization ([583db7c](https://github.com/xsolla/pay-station-sdk/commit/583db7c21768d6e9ae44c21ea5314af53e6b424c)) +- **PAYMENTS-15244:** add payment-form component ([2d154ad](https://github.com/xsolla/pay-station-sdk/commit/2d154addce263e13d2e03948b934539d9eff6a62)) +- **PAYMENTS-15245:** add legal component ([b295418](https://github.com/xsolla/pay-station-sdk/commit/b2954181b92b8a02f2d466ee8a446a6fa336e37c)) +- **PAYMENTS-15270:** form - activate & getStatus ([23d28ef](https://github.com/xsolla/pay-station-sdk/commit/23d28ef1b2c9c6ea4d96292b7c068c02ad376bd3)) +- **PAYMENTS-15270:** form - activate & getStatus + tests ([edf95d7](https://github.com/xsolla/pay-station-sdk/commit/edf95d7cc7bf466b827354710903450306fbd2d4)) +- **PAYMENTS-15271:** show errors show field state actions ([1b31e0e](https://github.com/xsolla/pay-station-sdk/commit/1b31e0eafd9f3a4910a0502440f43473bedc614c)) +- **PAYMENTS-16028:** get status without form init ([90fa9c9](https://github.com/xsolla/pay-station-sdk/commit/90fa9c9188e66a0a48d7bc1daa6394bf28229322)) +- **TextComponent:** show validation errors ([5e53dea](https://github.com/xsolla/pay-station-sdk/commit/5e53dea3ed364831aac7f04411af679af0aec307)) +- web components architecture implementation ([65b260e](https://github.com/xsolla/pay-station-sdk/commit/65b260ee12683f4a872390fb00c218ea4856a6ca)) + +### Bug Fixes + +- added legal disclaimer translation ([1d0240e](https://github.com/xsolla/pay-station-sdk/commit/1d0240ec73cca6570e53e7d96ea849cd53355ec2)) +- legal check ping messages ([c476097](https://github.com/xsolla/pay-station-sdk/commit/c476097723324a09446611cdb195283d58ec087c)) +- **PAYMENTS-15239:** change text component selector ([f1cbbe9](https://github.com/xsolla/pay-station-sdk/commit/f1cbbe9eb976dabace057e45baadde1442016d1d)) +- **PAYMENTS-15239:** emit selected method id on li element click ([d113dc8](https://github.com/xsolla/pay-station-sdk/commit/d113dc80352967214287e47094217e2626c12f85)) diff --git a/README.md b/README.md index b409b5ff..451fe188 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,642 @@ # Xsolla Pay Station SDK -
+- [What is Pay Station SDK?](#what-is-pay-station-sdk) +- [Installing with npm-package](#installing-with-npm-package) +- [General integration scheme](#general-integration-scheme) +- [PaymentSdk library interface](#paymentsdk-library-interface) +- [Pay Station SDK components](#pay-station-sdk-components) + - [Regular components](#regular-components) + - [Secure components](#secure-components) + - [Special components](#special-components) +- [Pay Station SDK supported languages](#pay-station-sdk-supported-languages) +- [Integration guide](#integration-guide) -## Integration Guide +--- -
+## What is Pay Station SDK? -### Installing with npm +Pay Station SDK is a fully customizable payment solution that allows you to manage payments in browsers or WebView via Xsolla without using Xsolla Pay Station. + +![Client integration scheme](./readme_images/client_web_app.png 'Integration scheme') + +The JavaScript SDK is loaded and initialized by the client. It then loads a 0 px iframe containing the Core Library that has access to sensitive user and payment data. This information is encapsulated in the iframe, but neither the client nor their third-party scripts can access this data. + +Access to public data is provided through public post messages. The Core Library makes requests to the Payments API, filters responses to remove sensitive data, converts the data into a usable format, and sends it to the client via public post messages. + +Inputs are encapsulated in an iframe, preventing the client from accessing the user's input values. + +When creating components, the client can use the JavaScript SDK Web Components with or without default styles. Additionally, the SDK events interface can be used to create custom components. + +Except for secure inputs, the client has full control over the component's styles, HTML, logic, and order. An iframe containing an input has a wrapper and events interface for complete customization. + +## Installing with npm-package `npm install --save @xsolla/pay-station-sdk` + +## General integration scheme + +![General integration scheme](./readme_images/general_integration_scheme.png 'General integration scheme') + +## PaymentSdk library interface + +```typescript +declare const headlessCheckout: { + /** + * Load secure Core Iframe. + * Load components to CustomElementRegistry. https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry + */ + init(environment: { isWebview?: boolean; sandbox?: boolean }): Promise; + + /** + * Remove secure Core Iframe. + */ + destroy(): void; + + /** + * Initialize the payment with a token. + * Reset the form on initialization. + */ + setToken(token: string): Promise; + + /** + * Returns available payment methods, excluding quick payment options. + */ + getRegularMethods(country?: string): Promise; + + /** + * Returns available quick payment options, e.g., Apple Pay, Google Pay. + */ + getQuickMethods(country?: string): Promise; + + /** + * Returns available payment methods, including quick payment options and saved payment methods. + */ + getCombinedPaymentMethods(country?: string): Promise; + + /** + * Returns user’s saved methods. + */ + getSavedMethods(): Promise; + + /** + * Returns a user’s balance. + */ + getUserBalance(): Promise; + + /** + * Returns financial information about the payment. + */ + getFinanceDetails(): Promise; + + /** + * Returns available countries. + */ + getCountryList(): Promise<{ + countryList: CountryResponse['countryList']; + currentCountry: string; + }>; + + /** + * Returns available locales. + */ + getAvailableLanguages(): Lang[]; + + /** + * Payment form. + * Only one form can exist on a page at a time. + */ + form: { + /** + * Callback to submit responses listening. + * Receive the NextAction object with instructions about what action is needed next to complete the payment. + * Actions: + * 1. Redirect to external payment system. + * 2. Process 3D-Secure. + * 3. Show form errors. + * 4. Show another form. + * 5. Check payment status for completion. + */ + onNextAction: (nextAction: NextAction) => void; + + /** + * Form statuses: + * 1. undefined - form is not initialized. Run form.init() to initialize. + * 2. active - form is ready for user interaction. + * 3. pending - form is waiting for the initialization and submission processes. + */ + getStatus(): 'undefined' | 'active' | 'pending'; + + /** + * Initialize the form. Changes the form to the pending status. + * Most payment methods require redirection to an external payment system. + * returnUrl setting allows the payment system to redirect to this URL after the payment is completed. + * accountId setting enables payment with a saved method. + * isSaveMode setting allows you to save payment methods. + * @throws InvalidConfiguration if accountId and isSaveMode are both defined. + */ + init(formConfiguration: { + paymentMethodId: number; + returnUrl: string; + country?: string; + accountId?: number; + isSaveMode?: boolean; + }): Promise<{ + fields: Field[]; + financeDetails: FinanceDetails; + }>; + + /** + * Set form status - active. + * The method activates the payment form to make the form available to a user. + * Allows you to prepare components before a user can interact with them. + */ + activate(): void; + }; + + /** + * Get the final payment status: Success or Error. + * Calling this method breaks the previous connection. + * Use the invoiceId argument after a return from the payment system. + * @throws UndefinedFormError if you did not pass invoiceId and the initialized form does not exist. + * @throws BreakConnectionError if the method is called again. + */ + getStatus(invoiceId?: number): Promise; + + /** + * Can be used instead of SDK components for creating custom components. + * List of events will be added later. + * Events between an app and Core Iframe. + */ + events: { + onCoreEvent: (event: Event) => void; + send(event: Event): void; + }; +}; +``` + +## Pay Station SDK components + +### Regular components + +| **Component** | **Selector** | **Status** | +| --------------------- | -------------------------- | ---------- | +| Payment Methods | psdk-payment-methods | ✅ | +| Saved Methods | psdk-saved-methods | ✅ | +| Payment Form Messages | psdk-payment-form-messages | ✅ | +| Checkbox | psdk-checkbox | ✅ | +| Select | psdk-select | ✅ | +| Apple Pay Button | psdk-apple-pay | ✅ | +| Google Pay Button | psdk-google-pay-button | ✅ | +| Submit Button | psdk-submit-button | ✅ | +| User Balance | psdk-user-balance | ✅ | +| Finance Details | psdk-finance-details | ✅ | +| Status | psdk-status | ✅ | + +![Regular SDK web components](./readme_images/sdk_web_components_scheme.png 'Regular SDK web components') + +Using SDK components is straightforward: you only need to paste the HTML tag of the component. SDK components are based on Web Components ([learn more](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)), and do not work with older browsers like Internet Explorer 11. But if you want to use them or want total control of HTML, you can implement your own component based on public events instead of using SDK components. + +![SDK component state sync](./readme_images/component_state_sync.png 'SDK component state sync') + +
+ +### Secure components + +| **Component** | **Selector** | **Status** | +| --------------------- | ---------------- | ---------- | +| Text Component | psdk-text | ✅ | +| Phone Component | psdk-phone | ✅ | +| Card Number Component | psdk-card-number | ✅ | + +![SDK secure componentscheme](./readme_images/secure_component_scheme.png 'SDK secure componentscheme') + +Secure components have access to sensitive user data, and are encapsulated in iframes. But the input iframe consists of HTML input only and can receive styles for customization. Client have access to other HTML components like errors, borders, title, icons, etc., that exist outside of the iframe (Web Component). + +![Customizing secure components](./readme_images/customizing_secure_components.png 'Customizing secure components') + +
+ +### Special components + +| **Component** | **Selector** | **Status** | +| ------------- | ----------------- | ---------- | +| Legal | psdk-legal | ✅ | +| Payment Form | psdk-payment-form | ✅ | +| ThreeDs | psdk-3ds | ✅ | + +The `Payment Form` component creates missed payment form components to ensure the client does not omit required payment form components. The client receives a warning message from the SDK, but still allows users to complete the payment. + +The `Legal` component contains information about Xsolla's legal documents. Client has to include this component and display legal documents in their application in accordance with their agreement with Xsolla. Otherwise, the payment flow is blocked. + +The `ThreeDs` component contains the logic required to perform the necessary credit card checks in accordance with the 3DS procedure. + +## Pay Station SDK supported languages + +- Arabic +- Bulgarian +- Chinese (Simplified) +- Chinese (Traditional) +- Czech +- English +- French +- German +- Hebrew +- Italian +- Japanese +- Korean +- Polish +- Portuguese +- Romanian +- Russian +- Spanish +- Thai +- Turkish +- Vietnamese + +## Integration guide + +To start using the Pay Station SDK, include the SDK bundle in your project. You can do this in any convenient way, such as adding the SDK bundle as npm-package. + +> A working example can be found [here](./examples/select-method) + +Regardless of the SDK adding method chosen, all integration steps are the same: + +1. Include the SDK library in your project. It doesn't matter whether you are using an npm-package. +2. Access the `headlessCheckout` object, which contains the Pay Station initialization logic. +3. Initialize the SDK with your environment parameters. +4. Set the access token for the initialized SDK. +5. Place the Pay Station components in the HTML markup. +6. (Optional) Select the Pay Station components as regular HTML tags and subscribe on their events to implement additional logic using callbacks. + +```html + + + + + + + Document + + + + + +

Pay Station SDK

+ + + + + + + + +``` + +## PayPal integration guide + +> A working example can be found [here](./examples/paypal) + +Integration flow: + +1. Add the SDK library to your project. You can use an npm-package. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Add the `` component to the HTML markup to show purchase details. + - The financial details component will be updated with transaction details once the payment is completed. +1. Initialize the SDK with your environment parameters. +1. Set the access token for the initialized SDK. +1. Initialize the payment form with the PayPal payment method ID and return URL. + - The return URL is used to redirect the user once payment is completed on PayPal’s side. + - `headlessCheckout.form.init` method returns the form object that can be used for future work with the payment form. +1. Subscribe to events of the `NextActions` form to receive notifications about the next payment flow steps. + - Next action with the `redirect` type informs you that a redirect action is required on your side. You can get the URL to redirect from the action payload. +1. Add the form fields component to the HTML markup. + - Use the form object that was returned by `headlessCheckout.form.init` method to get form fields. + - Use fields with the `isMandatory` flag to get required fields only. + - Use the `` component to render form fields if required. Email and ZIP fields can only be required for PayPal. +1. Add the `` form submit button to the HTML markup. +1. Handle next action with the `redirect` type once the submit button is clicked. + - Use action payload to get the URL. Make sure you add query parameters to the URL from action payload data. + - Redirect the user to the PayPal payment system using the generated URL. + - You can redirect to the PayPal URL in the same window or create a new window and keep the payment form in a separate tab. Once payment is completed on PayPal’s side, a user is redirected to `returnUrl`. +1. Add the `` component to the HTML markup to see the payment status. + +## Credit card integration guide + +> A working example can be found [here](./examples/credit-card). + +Integration flow: + +1. Add the SDK library to your project. You can use an npm package. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Add the `` component to the HTML markup to show purchase details. + - The financial details component are updated with transaction details once the payment is completed. +1. Initialize the SDK with your environment parameters. +1. Set the access token for the initialized SDK. +1. Initialize the payment form with the Credit Card payment method ID and return URL (necessary for 3-D Secure transactions). + - The return URL redirects the user once payment is completed on the 3-D Secure’s side. + - The `headlessCheckout.form.init` method returns the form object that can be used for future work with the payment form. +1. Subscribe to events of the `NextActions` form to receive notifications about the next payment flow steps. + - The Next action with the `show_fields` type means that the form needs to render extra fields, e.g., for Brazilian credit cards. A partner must remove previously added fields and render new fields for this step. + - The Next action with the `redirect` type means the form is redirected to complete payment according to the **3DS 1.0** secure procedure. The correct return URL must be provided to return from the 3-D Secure verification flow when it is completed. + - The Next action with the `3DS` type means the user card must be checked according to the **3DS 2.0** procedure. The partner is responsible for opening a 3-D Secure window. +1. Add the form fields component to the HTML markup. + - Use the form object that was returned by `headlessCheckout.form.init` method to get form fields. + - Use fields with the `isMandatory` flag to get required fields only. + - Use the `` component to render form fields if required. For a field named `card_number` you must use the `` component. +1. Add the `` form submit button to the HTML markup. +1. Add the `` component to the HTML markup to see the payment status. +1. Create a `return` page. +1. Add the ``, `` and `` components to the created `return` page to show a payment status. +1. Set accessToken at `headlessCheckout.setToken`. Run `headlessCheckout.init` to initialize the headless checkout library. + +## Google Pay integration guide + +> A working example can be found [here](./examples/google-pay). + +Integration flow: + +1. Add the SDK library to your project. You can use an npm package. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Add the `` component to the HTML markup to show purchase details. + - The financial details component are updated with transaction details once the payment is completed. +1. Initialize the SDK with your environment parameters. +1. Set the access token for the initialized SDK. +1. Initialize the payment form with the Google Pay payment method ID and return URL. + - The `headlessCheckout.form.init` method returns the form object that can be used for future work with the payment form. +1. Subscribe to events of the `NextActions` form to receive notifications about the next payment flow steps. + - The Next action with the `check_status` type means that you need to render the status component. + - The Next action with the `special_button` type means that you need to render the special button component (in our case it is the Google Pay button). +1. Add a form message component to the form - ``. +1. Add a payment form component to the form - ``. +1. Add the `` form submit button to the HTML markup. +1. Create a `return` page. +1. Add the ``, ``, and `` components to the created `return` page to show a payment status. +1. Set accessToken at `headlessCheckout.setToken`. Run `headlessCheckout.init` to initialize the headless checkout library. + +## Apple Pay integration guide + +> A working example can be found [here](./examples/apple-pay). + +Features of the payment system: + +1. An Apple Pay payment can only be made on a supported device. +1. On the `PayStationSdk` side, only the possibility of payment on this device is checked, all necessary data is checked, and a new tab is opened where the payment itself will be made - https://secure.xsolla.com/paystation4/payment/external-pages/apple-pay. +1. For the convenience of users, the `PayStationSdk` the `` component is integrated into the `` component. + +Integration flow: + +1. Add the SDK library to your project. You can use an npm package. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Add the `` component to the HTML markup to show purchase details. + +- The financial details component are updated with transaction details once the payment is completed. + +1. Subscribe to events of the `NextActions` form to receive notifications about the next payment flow steps. + +- The Next action with the `check_status` type means that you need to render the status component. +- The Next action with the `hide_form` type informs you that can hide payment form with css. +- The Next action with the `show_errors` type means that you need to show errors messages and payment can't be completed. + +1. Add a payment form component to the form - ``. +1. Initialize the payment form with the Apple-pay payment method ID and return URL. + +- The `headlessCheckout.form.init` method returns the form object that can be used for future work with the payment form. + +1. Add the `` form submit button to the HTML markup. + +## SDK payment methods integration guide (Barzahlen, Naver, Venmo, etc.) + +> A working example can be found [here](./examples/sdk-payment-methods). + +Integration flow: + +1. Add the SDK library to your project. You can use an npm package. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Add the `` component to the HTML markup to show purchase details. + - The financial details component are updated with transaction details once the payment is completed. +1. Initialize the SDK with your environment parameters. +1. Set the access token for the initialized SDK. +1. Initialize the payment form with the SDK payment method ID and return URL. + - The `headlessCheckout.form.init` method returns the form object that can be used for future work with the payment form. +1. Subscribe to events of the `NextActions` form to receive notifications about the next payment flow steps. + - Next action with the `redirect` type informs you that a redirect action is required on your side. You can get the redirect URL from the action payload. +1. Add a form message component to the form - ``. +1. Add a payment form component to the form - ``. +1. Add the `` form submit button to the HTML markup. +1. Create a `return` page. +1. Add the ``, ``, and `` components to the created `return` page to show a payment status. +1. Set accessToken at `headlessCheckout.setToken`. Run `headlessCheckout.init` to initialize the headless checkout library. + +## QR code payment integration guide + +> A working example can be found [here](./examples/qr-code). + +Integration flow: + +1. Add the SDK library to your project. You can use an npm package. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Add the `` component to the HTML markup to show purchase details. + - The financial details component are updated with transaction details once the payment is completed. +1. Initialize the SDK with your environment parameters. +1. Set the access token for the initialized SDK. +1. Initialize the payment form with the QR code payment method ID and return URL. + - The `headlessCheckout.form.init` method returns the form object that can be used for future work with the payment form. +1. Subscribe to events of the `NextActions` form to receive notifications about the next payment flow steps. + - The Next action with the `redirect` type informs you that a redirect action is required on your side. You can get the redirect URL from the action payload. + - The Next action with the `show_qr_code` type means that you need to render the QR code component. + - The Next action with the `check_status` type means that you need to render the status component. +1. Add a form message component to the form - ``. +1. Add a payment form component to the form - ``. +1. Add the `` form submit button to the HTML markup. +1. Create a `return` page. +1. Add the ``, ``, and `` components to the created `return` page to show a payment status. +1. Set accessToken at `headlessCheckout.setToken`. Run `headlessCheckout.init` to initialize the headless checkout library. + +## Mobile payment integration guide + +> A working example can be found [here](./examples/mobile-payment). + +Integration flow: + +1. Add the SDK library to your project. You can use an npm package. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Add the `` component to the HTML markup to show purchase details. + - The financial details component are updated with transaction details once the payment is completed. +1. Initialize the SDK with your environment parameters. +1. Set the access token for the initialized SDK. +1. Initialize the payment form with the QR code payment method ID and return URL. + - The `headlessCheckout.form.init` method returns the form object that can be used for future work with the payment form. +1. Subscribe to events of the `NextActions` form to receive notifications about the next payment flow steps. + - The Next action with the `redirect` type informs you that a redirect action is required on your side. You can get the redirect URL from the action payload. + - The Next action with the `show_mobile_payment_screen` type means that you need to render new submit button. + - The Next action with the `check_status` type means that you need to render the status component. +1. Add a form message component to the form - ``. +1. Add a payment form component to the form - ``. +1. Add the `` form submit button to the HTML markup. +1. Create a `return` page. +1. Add the ``, ``, and `` components to the created `return` page to show a payment status. +1. Set accessToken at `headlessCheckout.setToken`. Run `headlessCheckout.init` to initialize the headless checkout library. + +## Payment via user balance + +1. If the user has sufficient virtual balance for full payment, a form for payment by balance will always be returned, regardless of the method passed when initiating the payment form using `headlessCheckout.form.init({...})`. +2. Payment by balance will be reflected in the `` component. +3. The user balance can also be displayed using the `` component. +4. The user balance can be obtained using the `headlessCheckout.getUserBalance()` method. + +## Saved payment methods + +### Save method integration guide + +> A working example can be found [here](./examples/save-payment-method). + +Integration flow: + +1. Add the SDK library to your project. You can use an npm-package or CDN link. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Display methods that can be saved ``. +1. Handle the selection of the payment method with `selectionChange` event. +1. Add a form message component to the form - ``. +1. Add a payment form component to the form - ``. +1. Initialize the payment form with the `{ paymentMethodId: ${id}, savePaymentMethod: true, returnUrl: ${returnUrl}}` parameters, where `event.detail.paymentMethodId` is from the `selectionChange` event. +1. Handle the next action event. + +### Delete saved method integration guide + +> A working example can be found [here](./examples/saved-methods). + +Integration flow: + +1. Add the SDK library to your project. You can use an npm-package or CDN link. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Display saved methods using the `` component. +1. To make the delete saved method button appear, you need to add the `delete-mode='true'` attribute to the `` component. +1. Handle the deleted saved method event - `deletedSavedMethodStatus`. This is optional. It can be used to display an informational message. + +### Payment via saved method integration guide + +> A working example can be found [here](./examples/payment-via-saved-method). + +Integration flow: + +1. Add the SDK library to your project. You can use an npm-package or CDN link. +1. Access the `headlessCheckout` object that contains the Pay Station initialization logic. +1. Add the `` component to the HTML markup to provide links to legal documents. +1. Display saved methods using the `` component. +1. Handle the selection of the payment method with the `savedMethodSelected` event. +1. Add a form message component to the form - ``. +1. Add a payment form component to the form - ``. +1. Initialize the payment form with the `{ paymentMethodId: ${id}, paymentWithSavedMethod: true, savedMethodId, returnUrl: ${returnUrl}}` parameters, where `paymentMethodId` and `savedMethodId` are from the `savedMethodSelected` of the `event.details` parameter. +1. Handle the next action event. + +## Payment country + +### Changing country + +> A working example can be found [here](./examples/changing-country). + +Where can the country value be used? + +1. When initializing the form using `headlessCheckout.form.init({ ..., country: ${countryISO} })`. +2. When passing it as an attribute for the ` + + + + + Document + + + + + + + + +

Apple pay

+
+
+
+ + + + + + diff --git a/examples/apple-pay/style.css b/examples/apple-pay/style.css new file mode 100644 index 00000000..ccb72c16 --- /dev/null +++ b/examples/apple-pay/style.css @@ -0,0 +1,21 @@ +.hidden { + visibility: hidden; + position: absolute; +} + +.error { + padding: 12px; + margin-bottom: 1rem; + border-radius: var(--psdk-common-border-radius); + color: var(--psdk-alert-color); + background: var(--psdk-alert-bg); + font-family: var(--psdk-font-family); + font-size: var(--psdk-font-size); + line-height: var(--psdk-line-height); +} + +.error a, +.error a:hover, +.error a:visited { + color: var(--psdk-alert-color); +} diff --git a/examples/changing-country/index.html b/examples/changing-country/index.html new file mode 100644 index 00000000..877bcf6b --- /dev/null +++ b/examples/changing-country/index.html @@ -0,0 +1,79 @@ + + + + + + + Document + + + + + + + +

Pay Station SDK

+ + + + + + diff --git a/examples/credit-card/index.html b/examples/credit-card/index.html new file mode 100644 index 00000000..86332aca --- /dev/null +++ b/examples/credit-card/index.html @@ -0,0 +1,54 @@ + + + + + + + Credit card integration + + + + + + + + + + + +
+

Credit card integration

+
+
+ + + +
+ +
+ +
+ +
+
+
+ + + +
+ + diff --git a/examples/credit-card/init-payment-flow.js b/examples/credit-card/init-payment-flow.js new file mode 100644 index 00000000..4eadfc4a --- /dev/null +++ b/examples/credit-card/init-payment-flow.js @@ -0,0 +1,255 @@ +/** + * Run the `buildPaymentFlow` function on `DOMContentLoaded` to access all the DOM nodes needed. + */ +document.addEventListener('DOMContentLoaded', buildPaymentFlow); + +function buildPaymentFlow() { + if (typeof PayStationSdk === 'undefined') { + alert(payStationSdkUndefinedError); + throw new Error('PayStationSdk not found'); + } + /** + * To learn more about creating tokens, + * refer to our documentation https://developers.xsolla.com/api/pay-station/operation/create-token/ + */ + const accessToken = ''; + + if (!accessToken) { + alert('No token provided. Please, check the documentation'); + throw new Error('No token provided'); + } + + /** + * The SDK is available under the PayStationSdk namespace. + * To begin initialization, obtain a reference to the headlessCheckout object. + */ + const { headlessCheckout } = PayStationSdk; + + function handleRedirectAction(redirectAction) { + /** + * Handle redirect to 3-D Secure procedure. + */ + const url = new URL(redirectAction.data.redirect.redirectUrl); + const params = Object.entries(redirectAction.data.redirect.data); + params.forEach((entry) => { + const [key, value] = entry; + url.searchParams.append(key, value); + }); + + /** + * Open 3-D Secure. + */ + this.window.location.href = url.toString(); + } + + function renderRequiredFields(requiredFields, formElement) { + /** + * It is important to render every every required field as a component according to its own type or + * other specific parameters. In the current case, we could encounter fields of the following types: + * 'text', 'select', or 'text' with the name 'card_number'. + * + * Every type is mapped to a suitable component, and then the component is rendered in the DOM. + */ + requiredFields.forEach((field) => { + if (field.type === 'text' && field.name === 'card_number') { + renderCardNumberComponent(formElement, field); + return; + } + if (field.type === 'text') { + renderTextComponent(formElement, field); + return; + } + if (field.type === 'select') { + renderSelectComponent(formElement, field); + return; + } + }); + } + + function renderCardNumberComponent(formElement, field) { + /** + * You can use as well. + */ + const input = new PayStationSdk.CardNumberComponent(); + input.setAttribute('name', field.name); + formElement.append(input); + } + + function renderSelectComponent(formElement, field) { + /** + * You can use as well. + */ + const input = new PayStationSdk.SelectComponent(); + input.setAttribute('name', field.name); + formElement.append(input); + } + + function renderTextComponent(formElement, field) { + /** + * You can use as well. + */ + const input = new PayStationSdk.TextComponent(); + input.setAttribute('name', field.name); + formElement.append(input); + } + + function renderSubmitButton(formElement) { + /** + * Render the submit form button. + * You can use as well. + */ + const submitButton = new PayStationSdk.SubmitButtonComponent(); + submitButton.setAttribute('text', 'Pay Now'); + formElement.append(submitButton); + } + + function handle3dsAction(threeDsAction) { + /** + * Create the 3-D Secure component. It will handle the 3-D Secure verification flow. + * You can use the component as well. + */ + + const threeDsComponent = new PayStationSdk.ThreeDsComponent(); + + threeDsComponent.setAttribute( + 'data-challenge', + JSON.stringify(threeDsAction.data.data), + ); + + document.getElementById('right-col').append(threeDsComponent); + } + + function renderStatusComponent(statusElement) { + /** + * Create the status component. It will be updated once a payment status changed. + * You can use the component as well. + */ + const statusComponent = new PayStationSdk.StatusComponent(); + statusElement.append(statusComponent); + } + + function clearFormFields(formElement) { + /** + * In some cases, we need to remove all form fields that were rendered before. + * This may be necessary when processing Brazilian credit cards, because + * they also have a second step with extra fields that need to be filled. + * To create a fluent user experience, clear submitted fields and + * render new fields in the same place. + */ + formElement.innerHTML = ''; + } + + function getRequiredFields(fields) { + /** + * form.fields provide available fields for the selected payment method. + * You can filter it by the `isMandatory` flag to get required fields only. + */ + return fields.filter((field) => field.isMandatory === '1'); + } + + async function initPayStationSdk() { + /** + * Call the `init()` method with the provided environment object. + * The isWebView parameter is required and indicates whether your + * integration type is a WebView. + * You can set sandbox payment mode with the `sandbox` parameter. + * Note that this command executes asynchronously. + */ + await headlessCheckout.init({ + isWebView: false, + sandbox: false, + }); + + /** + * Set styles for secure components. + */ + await headlessCheckout.setSecureComponentStyles(` + @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap'); + `); + + /** + * After the Payments SDK has been initialized, the next step is setting the token. + * For more information about creating tokens, + * refer to our documentation https://developers.xsolla.com/api/pay-station/operation/create-token/ + */ + await headlessCheckout.setToken(accessToken); + + /** + * Define payment method ID. + * To get lists of payment methods, use psdk-payment-methods. + * Refer to `examples/select-method` for more details. + */ + const creditCardMethodId = 1380; + + /** + * Initialize payment. + * `returnUrl` is opened after the payment is completed on 3-D Secure’s side. + */ + const form = await headlessCheckout.form.init({ + paymentMethodId: creditCardMethodId, + /* + * This return URL means you start the current example on localhost with the 3000 port. + * */ + returnUrl: 'http://localhost:3000/pay-station-sdk/examples/return.html', + }); + + /** + * Retrieving DOM elements to render form fields and display status messages. + */ + const formElement = document.querySelector('#form-container'); + const statusElement = document.querySelector('#status-container'); + + /** + * Subscribe to payment actions. + */ + headlessCheckout.form.onNextAction((nextAction) => { + switch (nextAction.type) { + /** + * Handle the 'show_fields' action. + */ + case 'show_fields': + clearFormFields(formElement); + renderRequiredFields(nextAction.data.fields, formElement); + renderSubmitButton(formElement); + break; + + /** + * Handle the 'check_status' action. + */ + case 'check_status': + /** + * Remove unnecessary form fields to render StatusComponent in the same place. + */ + clearFormFields(formElement); + renderStatusComponent(statusElement); + break; + + /** + * Handle the '3DS' action. + */ + case '3DS': + handle3dsAction(nextAction); + break; + + /** + * Handle the 'redirect' action. + */ + case 'redirect': + handleRedirectAction(nextAction); + break; + } + }); + + const requiredFields = getRequiredFields(form.fields); + + /** + * Render the required fields. + */ + renderRequiredFields(requiredFields, formElement); + + renderSubmitButton(formElement); + } + + // Initialize SDK. + initPayStationSdk(); +} diff --git a/examples/credit-card/style.css b/examples/credit-card/style.css new file mode 100644 index 00000000..0b192996 --- /dev/null +++ b/examples/credit-card/style.css @@ -0,0 +1,73 @@ +/* *** *** COMMON: START *** *** */ + +.application { + margin: 0 auto; + width: 100%; + max-width: 700px; +} + +.columns-wrapper { + display: flex; + padding-bottom: 20px; +} + +.left-col, +.right-col { + width: 50%; +} + +/* *** *** COMMON: END *** *** */ + +/* *** *** FINANCE DETAILS: START *** *** */ + +psdk-finance-details, psdk-total { + display: block; + background: #f5f5f5; + margin-right: 10px; + padding: 10px; +} + +.subtotal-row, +.total-row { + display: flex; + width: 100%; + justify-content: space-between; + margin: 10px 0; +} + +/* *** *** FINANCE DETAILS: END *** *** */ + +/* *** *** STATUS: START *** *** */ +psdk-status { + display: flex; +} + +psdk-status .image { + display: block; + width: 100%; + height: auto; +} + +psdk-status .title-text { + text-align: center; +} + +/* *** *** STATUS: END *** *** */ + +/* *** *** FOOTER LINKS: START *** *** */ +.company { + padding-top: 5px; + display: flex; +} + +.company .logo { + margin-right: 3px; +} + +.legal-links { + padding-top: 5px; + display: flex; + justify-content: space-between; +} + +/* *** *** FOOTER LINKS: END *** *** */ diff --git a/examples/default-styles/index.html b/examples/default-styles/index.html new file mode 100644 index 00000000..6eb73b26 --- /dev/null +++ b/examples/default-styles/index.html @@ -0,0 +1,70 @@ + + + + + + + Document + + + + + + + +

Pay Station SDK

+ + + + + diff --git a/examples/google-pay/index.html b/examples/google-pay/index.html new file mode 100644 index 00000000..323c8911 --- /dev/null +++ b/examples/google-pay/index.html @@ -0,0 +1,175 @@ + + + + + + + Document + + + + + + + + +

Pay Station SDK

+ +

Google Pay payment integration

+ +
+
+ + + + + + + + + + + diff --git a/examples/google-pay/style.css b/examples/google-pay/style.css new file mode 100644 index 00000000..2b2dc534 --- /dev/null +++ b/examples/google-pay/style.css @@ -0,0 +1,15 @@ +/* *** *** COMMON: START *** *** */ + +.application { + margin: 0 auto; + width: 100%; + max-width: 700px; +} + +#form-container { + display: flex; + flex-direction: column; + align-items: center; +} + +/* *** *** COMMON: END *** *** */ diff --git a/examples/mobile-payment/index.html b/examples/mobile-payment/index.html new file mode 100644 index 00000000..ff4a9c4a --- /dev/null +++ b/examples/mobile-payment/index.html @@ -0,0 +1,220 @@ + + + + + + + Document + + + + + + + +
+

Pay Station SDK

+ +

Mobile payment methods integration

+ + + +
+
+ + + + + + + + +
+ + + diff --git a/examples/mobile-payment/style.css b/examples/mobile-payment/style.css new file mode 100644 index 00000000..2b2dc534 --- /dev/null +++ b/examples/mobile-payment/style.css @@ -0,0 +1,15 @@ +/* *** *** COMMON: START *** *** */ + +.application { + margin: 0 auto; + width: 100%; + max-width: 700px; +} + +#form-container { + display: flex; + flex-direction: column; + align-items: center; +} + +/* *** *** COMMON: END *** *** */ diff --git a/examples/payment-form/index.html b/examples/payment-form/index.html new file mode 100644 index 00000000..e0b5a92d --- /dev/null +++ b/examples/payment-form/index.html @@ -0,0 +1,138 @@ + + + + + + + Document + + + + + + + + +
+

Pay Station SDK

+ +

Payment Form component integration

+ +
+
+ + + + + + + + +
+ + + + diff --git a/examples/payment-form/style.css b/examples/payment-form/style.css new file mode 100644 index 00000000..c0acfefd --- /dev/null +++ b/examples/payment-form/style.css @@ -0,0 +1,134 @@ +/* *** *** COMMON: START *** *** */ + +.application { + margin: 0 auto; + width: 100%; + max-width: 360px; +} + +.columns-wrapper { + display: flex; + padding-bottom: 20px; +} + +.left-col, +.right-col { + width: 50%; +} + +/* *** *** COMMON: END *** *** */ + +/* *** *** CONTROLS: START *** *** */ +psdk-text, +psdk-card-number { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 360px; + margin-bottom: 30px; +} + +psdk-card-number .wrapper { + align-items: center; +} + +psdk-text iframe, +psdk-card-number iframe { + border: none; + width: 360px; +} + +.field-error { + color: #f30; +} + +/* Select style: the drop-down list will be hidden. */ +psdk-select button.select { + width: 100%; + max-width: 200px; +} + +.dropdown-wrapper { + display: none; +} + +/* Select style: the drop-down list will be opened. */ +.dropdown-wrapper.dropdown-opened { + display: block; +} + +.dropdown { + border: 1px solid #c2c2c2; + border-radius: 8px; +} + +.dropdown .select-options .option { + padding: 4px 8px; + cursor: pointer; + display: block; +} + +.dropdown .select-options .option:hover { + background: #e8e8e8; +} + +/* *** *** CONTROLS: END *** *** */ + +/* *** *** FINANCE DETAILS: START *** *** */ + +psdk-finance-details { + display: block; + background: #f5f5f5; + padding: 10px; +} + +psdk-total { + display: block; + background: #f5f5f5; + padding: 10px; +} + +.subtotal-row, +.total-row { + display: flex; + width: 100%; + justify-content: space-between; + margin: 10px 0; +} + +/* *** *** FINANCE DETAILS: END *** *** */ + +/* *** *** STATUS: START *** *** */ +psdk-status { + display: flex; +} + +psdk-status .image { + display: block; + width: 100%; + height: auto; +} + +psdk-status .title-text { + text-align: center; +} + +/* *** *** STATUS: END *** *** */ + +/* *** *** FOOTER LINKS: START *** *** */ +.company { + padding-top: 5px; + display: flex; +} + +.company .logo { + margin-right: 3px; +} + +.legal-links { + padding-top: 5px; + display: flex; + justify-content: space-between; +} + +/* *** *** FOOTER LINKS: END *** *** */ diff --git a/examples/payment-via-saved-method/index.html b/examples/payment-via-saved-method/index.html new file mode 100644 index 00000000..8e890790 --- /dev/null +++ b/examples/payment-via-saved-method/index.html @@ -0,0 +1,194 @@ + + + + + + + Document + + + + + + + + +

Save payment method

+
+
+ + + + + + diff --git a/examples/payment-via-saved-method/style.css b/examples/payment-via-saved-method/style.css new file mode 100644 index 00000000..551a6741 --- /dev/null +++ b/examples/payment-via-saved-method/style.css @@ -0,0 +1,12 @@ +psdk-saved-methods, +psdk-submit-button, +psdk-payment-form, +psdk-payment-form-messages { + display: block; + margin-bottom: 20px; + max-width: auto; +} + +psdk-submit-button button { + max-width: 100% !important; +} diff --git a/examples/paypal/index.html b/examples/paypal/index.html new file mode 100644 index 00000000..1596dab6 --- /dev/null +++ b/examples/paypal/index.html @@ -0,0 +1,172 @@ + + + + + + + PayPal integration + + + + + + +

PayPal payment integration

+ +
+
+ + + + + + + + + + + + diff --git a/examples/qr-code/index.html b/examples/qr-code/index.html new file mode 100644 index 00000000..eba9aca6 --- /dev/null +++ b/examples/qr-code/index.html @@ -0,0 +1,221 @@ + + + + + + + Document + + + + + + + +
+

Pay Station SDK

+ +

Qr code payment methods integration

+ + + +
+
+ + + + + + + + +
+ + + diff --git a/examples/qr-code/style.css b/examples/qr-code/style.css new file mode 100644 index 00000000..2b2dc534 --- /dev/null +++ b/examples/qr-code/style.css @@ -0,0 +1,15 @@ +/* *** *** COMMON: START *** *** */ + +.application { + margin: 0 auto; + width: 100%; + max-width: 700px; +} + +#form-container { + display: flex; + flex-direction: column; + align-items: center; +} + +/* *** *** COMMON: END *** *** */ diff --git a/examples/return.html b/examples/return.html new file mode 100644 index 00000000..83425a64 --- /dev/null +++ b/examples/return.html @@ -0,0 +1,81 @@ + + + + + + + Return page + + + + + + +

PayPal payment return page

+ + + + + + + + + + diff --git a/examples/save-payment-method/index.html b/examples/save-payment-method/index.html new file mode 100644 index 00000000..66921727 --- /dev/null +++ b/examples/save-payment-method/index.html @@ -0,0 +1,185 @@ + + + + + + + Document + + + + + + + + +

Save payment method

+
+
+ + + + + + diff --git a/examples/save-payment-method/style.css b/examples/save-payment-method/style.css new file mode 100644 index 00000000..8ca75b3e --- /dev/null +++ b/examples/save-payment-method/style.css @@ -0,0 +1,12 @@ +psdk-payment-methods, +psdk-submit-button, +psdk-payment-form, +psdk-payment-form-messages { + display: block; + margin-bottom: 20px; + max-width: auto; +} + +psdk-submit-button button { + max-width: 100% !important; +} diff --git a/examples/saved-methods/index.html b/examples/saved-methods/index.html new file mode 100644 index 00000000..3040de3b --- /dev/null +++ b/examples/saved-methods/index.html @@ -0,0 +1,105 @@ + + + + + + + Document + + + + + + + +

Pay Station SDK

+ + + + + + + + + diff --git a/examples/saved-methods/style.css b/examples/saved-methods/style.css new file mode 100644 index 00000000..85619696 --- /dev/null +++ b/examples/saved-methods/style.css @@ -0,0 +1,13 @@ +#edit-saved-methods { + font-size: var(--psdk-font-size); + font-family: var(--psdk-font-family); + line-height: var(--psdk-line-height); + margin-top: 20px; + width: 100%; + max-width: 360px; + height: 40px; + border-radius: var(--psdk-button-border-radius); + background: var(--psdk-button-default-bg); + color: var(--psdk-button-color); + font-weight: 500; +} diff --git a/examples/sdk-payment-methods/index.html b/examples/sdk-payment-methods/index.html new file mode 100644 index 00000000..9966aeee --- /dev/null +++ b/examples/sdk-payment-methods/index.html @@ -0,0 +1,196 @@ + + + + + + + Document + + + + + + + + +
+

Pay Station SDK

+ +

SDK payment methods integration

+ +
    +
  • Barzahlen
  • +
  • Naver
  • +
  • Venmo
  • +
  • etc
  • +
+ + + +
+
+ + + + + + + + +
+ + + diff --git a/examples/sdk-payment-methods/style.css b/examples/sdk-payment-methods/style.css new file mode 100644 index 00000000..2b2dc534 --- /dev/null +++ b/examples/sdk-payment-methods/style.css @@ -0,0 +1,15 @@ +/* *** *** COMMON: START *** *** */ + +.application { + margin: 0 auto; + width: 100%; + max-width: 700px; +} + +#form-container { + display: flex; + flex-direction: column; + align-items: center; +} + +/* *** *** COMMON: END *** *** */ diff --git a/examples/secure-component-styles/index.html b/examples/secure-component-styles/index.html new file mode 100644 index 00000000..1aa4931d --- /dev/null +++ b/examples/secure-component-styles/index.html @@ -0,0 +1,141 @@ + + + + + + + Secure component styles + + + + + + + + + + +

Secure component styles

+ +
+ + +
+ + + + + + + + + + diff --git a/examples/select-method/index.html b/examples/select-method/index.html new file mode 100644 index 00000000..1cb04916 --- /dev/null +++ b/examples/select-method/index.html @@ -0,0 +1,88 @@ + + + + + + + Document + + + + + + +

Pay Station SDK

+ + + + + + + + diff --git a/examples/variables.js b/examples/variables.js new file mode 100644 index 00000000..751fedbd --- /dev/null +++ b/examples/variables.js @@ -0,0 +1,2 @@ +const payStationSdkUndefinedError = `It seems the SDK library is undefined. + Please, link the PayStationSdk source or create a local build (recommended for testing purposes only).`; diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 190e194f..00000000 --- a/jest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - roots: ['/src'], - testMatch: [ - '**/tests/**/*.+(ts|tsx|js)', - '**/?(*.)+(spec|test).+(ts|tsx|js)', - ], - transform: { - '^.+\\.(ts|tsx)$': 'ts-jest', - }, -}; diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 00000000..3ce1ab2c --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,35 @@ +module.exports = function (config) { + config.set({ + basePath: './', + frameworks: ['jasmine', 'karma-typescript'], + files: ['src/**/*.ts'], + preprocessors: { + '**/*.ts': 'karma-typescript', + }, + karmaTypescriptConfig: { + compilerOptions: { + module: 'commonjs', + }, + tsconfig: './tsconfig.json', + }, + coverageOptions: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: -10, + }, + watermarks: { + branches: [65, 80], + functions: [65, 80], + lines: [65, 80], + statements: [65, 80], + }, + }, + reporters: ['progress', 'karma-typescript'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + browsers: ['ChromeHeadless'], + }); +}; diff --git a/lint/eslint/typescript.json b/lint/eslint/typescript.json index daa11fe7..7eb39bc1 100644 --- a/lint/eslint/typescript.json +++ b/lint/eslint/typescript.json @@ -144,8 +144,7 @@ "@typescript-eslint/no-confusing-non-null-assertion": "error", "no-dupe-class-members": "off", "@typescript-eslint/no-dupe-class-members": "error", - "no-duplicate-imports": "off", - "@typescript-eslint/no-duplicate-imports": "error", + "no-duplicate-imports": "error", "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-floating-promises": [ "error", @@ -154,7 +153,6 @@ "ignoreIIFE": true } ], - "@typescript-eslint/no-implicit-any-catch": "error", "no-implied-eval": "off", "@typescript-eslint/no-implied-eval": "error", "no-magic-numbers": "off", @@ -170,7 +168,7 @@ } ], "@typescript-eslint/no-misused-promises": "error", - "@typescript-eslint/no-non-null-assertion": "error", + "@typescript-eslint/no-non-null-assertion": "off", "no-shadow": "off", "@typescript-eslint/no-shadow": "error", "no-throw-literal": "off", diff --git a/lint/stylelint/scss.json b/lint/stylelint/scss.json index 5ec89cb0..b92c8188 100644 --- a/lint/stylelint/scss.json +++ b/lint/stylelint/scss.json @@ -11,8 +11,7 @@ } ], "color-function-notation": "legacy", - "alpha-value-notation": "number", - "property-disallowed-list": ["gap"] + "alpha-value-notation": "number" }, "overrides": [ { diff --git a/package-lock.json b/package-lock.json index dbb559ef..d7733013 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,46 +1,67 @@ { "name": "@xsolla/pay-station-sdk", - "version": "0.0.1", + "version": "0.0.22", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@xsolla/pay-station-sdk", - "version": "0.0.1", + "version": "0.0.22", "license": "ISC", + "dependencies": { + "currency-format": "^1.0.13", + "i18next": "^23.4.6", + "lit": "^3.1.3", + "tsyringe": "^4.8.0" + }, "devDependencies": { - "@types/jest": "^29.5.1", - "@types/prettier": "^2.7.2", - "@typescript-eslint/eslint-plugin": "^5.59.6", - "@typescript-eslint/parser": "^5.59.6", - "css-loader": "^6.7.4", - "eslint": "^8.40.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.7.0", + "@commitlint/cli": "^17.7.1", + "@commitlint/config-conventional": "^17.7.0", + "@types/jasmine": "^4.3.5", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", + "css-loader": "^6.8.1", + "eslint": "^8.48.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-n": "^16.0.2", "eslint-plugin-promise": "^6.1.1", "eslint-webpack-plugin": "^4.0.1", "husky": "^8.0.3", - "jest": "^29.5.0", - "lint-staged": "^13.2.2", + "karma": "^6.4.2", + "karma-chrome-launcher": "^3.2.0", + "karma-coverage": "^2.2.1", + "karma-jasmine": "^5.1.0", + "karma-typescript": "^5.5.4", + "lint-staged": "^14.0.1", "mini-css-extract-plugin": "^2.7.6", - "prettier": "2.8.8", + "prettier": "3.0.2", + "raw-loader": "^4.0.2", + "reflect-metadata": "^0.1.13", "resolve-url-loader": "^5.0.0", - "sass": "^1.62.1", - "sass-loader": "^13.3.0", - "stylelint": "^15.6.2", + "sass": "^1.66.1", + "sass-loader": "^13.3.2", + "stylelint": "^15.10.3", "stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-standard-scss": "^9.0.0", "stylelint-order": "^6.0.3", - "ts-jest": "^29.1.0", - "ts-loader": "^9.4.2", - "typescript": "^5.0.4", - "webpack": "^5.82.1", - "webpack-cli": "^5.1.1" + "ts-loader": "^9.4.4", + "typescript": "^5.2.2", + "webpack": "^5.82.2", + "webpack-cli": "^5.1.4" }, "engines": { "node": ">=16" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -55,42 +76,42 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.21.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz", - "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", - "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", + "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-compilation-targets": "^7.21.5", - "@babel/helper-module-transforms": "^7.21.5", - "@babel/helpers": "^7.21.5", - "@babel/parser": "^7.21.8", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -105,12 +126,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/@babel/core/node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -124,21 +139,21 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", - "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", "dev": true, "dependencies": { - "@babel/types": "^7.21.5", + "@babel/types": "^7.22.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -148,16 +163,16 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", - "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", + "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.21.5", - "@babel/helper-validator-option": "^7.21.0", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -176,9 +191,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -191,151 +206,142 @@ "dev": true }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", - "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", - "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", "dev": true, "dependencies": { - "@babel/types": "^7.21.4" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", - "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-module-imports": "^7.21.4", - "@babel/helper-simple-access": "^7.21.5", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", - "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-simple-access": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", - "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "dependencies": { - "@babel/types": "^7.21.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", - "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", + "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5" + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -415,9 +421,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", - "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -426,307 +432,458 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, + "node_modules/@babel/runtime": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "regenerator-runtime": "^0.14.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", + "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@commitlint/cli": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.7.1.tgz", + "integrity": "sha512-BCm/AT06SNCQtvFv921iNhudOHuY16LswT0R3OeolVGLk8oP+Rk9TfQfgjH7QPMjhvp76bNqGFEcpKojxUNW1g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@commitlint/format": "^17.4.4", + "@commitlint/lint": "^17.7.0", + "@commitlint/load": "^17.7.1", + "@commitlint/read": "^17.5.1", + "@commitlint/types": "^17.4.4", + "execa": "^5.0.0", + "lodash.isfunction": "^3.0.9", + "resolve-from": "5.0.0", + "resolve-global": "1.0.0", + "yargs": "^17.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@commitlint/config-conventional": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.7.0.tgz", + "integrity": "sha512-iicqh2o6et+9kWaqsQiEYZzfLbtoWv9uZl8kbI8EGfnc0HeGafQBF7AJ0ylN9D/2kj6txltsdyQs8+2fTMwWEw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "conventional-changelog-conventionalcommits": "^6.1.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", - "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "node_modules/@commitlint/config-validator": { + "version": "17.6.7", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-17.6.7.tgz", + "integrity": "sha512-vJSncmnzwMvpr3lIcm0I8YVVDJTzyjy7NZAeXbTXy+MPUdAr9pKyyg7Tx/ebOQ9kqzE6O9WT6jg2164br5UdsQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@commitlint/types": "^17.4.4", + "ajv": "^8.11.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@commitlint/config-validator/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/@commitlint/ensure": { + "version": "17.6.7", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-17.6.7.tgz", + "integrity": "sha512-mfDJOd1/O/eIb/h4qwXzUxkmskXDL9vNPnZ4AKYKiZALz4vHzwMxBSYtyL2mUIDeU9DRSpEUins8SeKtFkYHSw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@commitlint/types": "^17.4.4", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-17.4.0.tgz", + "integrity": "sha512-LIgYXuCSO5Gvtc0t9bebAMSwd68ewzmqLypqI2Kke1rqOqqDbMpYcYfoPfFlv9eyLIh4jocHWwCK5FS7z9icUA==", + "dev": true, + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@commitlint/format": { + "version": "17.4.4", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-17.4.4.tgz", + "integrity": "sha512-+IS7vpC4Gd/x+uyQPTAt3hXs5NxnkqAZ3aqrHd5Bx/R9skyCAWusNlNbw3InDbAK6j166D9asQM8fnmYIa+CXQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@commitlint/types": "^17.4.4", + "chalk": "^4.1.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@commitlint/is-ignored": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.7.0.tgz", + "integrity": "sha512-043rA7m45tyEfW7Zv2vZHF++176MLHH9h70fnPoYlB1slKBeKl8BwNIlnPg4xBdRBVNPaCqvXxWswx2GR4c9Hw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@commitlint/types": "^17.4.4", + "semver": "7.5.4" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@commitlint/lint": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.7.0.tgz", + "integrity": "sha512-TCQihm7/uszA5z1Ux1vw+Nf3yHTgicus/+9HiUQk+kRSQawByxZNESeQoX9ujfVd3r4Sa+3fn0JQAguG4xvvbA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@commitlint/is-ignored": "^17.7.0", + "@commitlint/parse": "^17.7.0", + "@commitlint/rules": "^17.7.0", + "@commitlint/types": "^17.4.4" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@commitlint/load": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.7.1.tgz", + "integrity": "sha512-S/QSOjE1ztdogYj61p6n3UbkUvweR17FQ0zDbNtoTLc+Hz7vvfS7ehoTMQ27hPSjVBpp7SzEcOQu081RLjKHJQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@commitlint/config-validator": "^17.6.7", + "@commitlint/execute-rule": "^17.4.0", + "@commitlint/resolve-extends": "^17.6.7", + "@commitlint/types": "^17.4.4", + "@types/node": "20.4.7", + "chalk": "^4.1.0", + "cosmiconfig": "^8.0.0", + "cosmiconfig-typescript-loader": "^4.0.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0", + "resolve-from": "^5.0.0", + "ts-node": "^10.8.1", + "typescript": "^4.6.4 || ^5.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/message": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.4.2.tgz", + "integrity": "sha512-3XMNbzB+3bhKA1hSAWPCQA3lNxR4zaeQAQcHj0Hx5sVdO6ryXtgUBGGv+1ZCLMgAPRixuc6en+iNAzZ4NzAa8Q==", + "dev": true, + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "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==", + "node_modules/@commitlint/parse": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.7.0.tgz", + "integrity": "sha512-dIvFNUMCUHqq5Abv80mIEjLVfw8QNuA4DS7OWip4pcK/3h5wggmjVnlwGCDvDChkw2TjK1K6O+tAEV78oxjxag==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@commitlint/types": "^17.4.4", + "conventional-changelog-angular": "^6.0.0", + "conventional-commits-parser": "^4.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", - "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "node_modules/@commitlint/read": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.5.1.tgz", + "integrity": "sha512-7IhfvEvB//p9aYW09YVclHbdf1u7g7QhxeYW9ZHSO8Huzp8Rz7m05aCO1mFG7G8M+7yfFnXB5xOmG18brqQIBg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@commitlint/top-level": "^17.4.0", + "@commitlint/types": "^17.4.4", + "fs-extra": "^11.0.0", + "git-raw-commits": "^2.0.11", + "minimist": "^1.2.6" }, "engines": { - "node": ">=6.9.0" + "node": ">=v14" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "17.6.7", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-17.6.7.tgz", + "integrity": "sha512-PfeoAwLHtbOaC9bGn/FADN156CqkFz6ZKiVDMjuC2N5N0740Ke56rKU7Wxdwya8R8xzLK9vZzHgNbuGhaOVKIg==", + "dev": true, + "dependencies": { + "@commitlint/config-validator": "^17.6.7", + "@commitlint/types": "^17.4.4", + "import-fresh": "^3.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "node_modules/@commitlint/rules": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.7.0.tgz", + "integrity": "sha512-J3qTh0+ilUE5folSaoK91ByOb8XeQjiGcdIdiB/8UT1/Rd1itKo0ju/eQVGyFzgTMYt8HrDJnGTmNWwcMR1rmA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@commitlint/ensure": "^17.6.7", + "@commitlint/message": "^17.4.2", + "@commitlint/to-lines": "^17.4.0", + "@commitlint/types": "^17.4.4", + "execa": "^5.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=v14" } }, - "node_modules/@babel/traverse": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", - "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.5", - "@babel/types": "^7.21.5", - "debug": "^4.1.0", - "globals": "^11.1.0" + "node_modules/@commitlint/to-lines": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-17.4.0.tgz", + "integrity": "sha512-LcIy/6ZZolsfwDUWfN1mJ+co09soSuNASfKEU5sCmgFCvX5iHwRYLiIuoqXzOVDYOy7E7IcHilr/KS0e5T+0Hg==", + "dev": true, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/top-level": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-17.4.0.tgz", + "integrity": "sha512-/1loE/g+dTTQgHnjoCy0AexKAEFyHsR2zRB4NWrZ6lZSMIxAhBJnmCqwao7b4H8888PsfoTBCLBYIw8vGnej8g==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=v14" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/@commitlint/types": { + "version": "17.4.4", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.4.4.tgz", + "integrity": "sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==", "dev": true, + "dependencies": { + "chalk": "^4.1.0" + }, "engines": { - "node": ">=4" + "node": ">=v14" } }, - "node_modules/@babel/types": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", - "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.21.5", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.1.1.tgz", - "integrity": "sha512-viRnRh02AgO4mwIQb2xQNJju0i+Fh9roNgmbR5xEuG7J3TGgxjnE95HnBLgsFJOJOksvcfxOUCgODcft6Y07cA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.1.tgz", + "integrity": "sha512-xrvsmVUtefWMWQsGgFffqWSK03pZ1vfDki4IVIIUxxDKnGBzqNgv0A7SB1oXtVNEkcVO8xi1ZrTL29HhSu5kGA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { "node": "^14 || ^16 || >=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.1.1" + "@csstools/css-tokenizer": "^2.2.0" } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz", - "integrity": "sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.0.tgz", + "integrity": "sha512-wErmsWCbsmig8sQKkM6pFhr/oPha1bHfvxsUY5CYSQxwyhA9Ulrs8EqCgClhg4Tgg2XapVstGqSVcz0xOYizZA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.4.tgz", - "integrity": "sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.4.tgz", + "integrity": "sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { "node": "^14 || ^16 || >=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1" + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0" } }, "node_modules/@csstools/selector-specificity": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", - "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", + "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { "node": "^14 || ^16 || >=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, "peerDependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.0.13" } }, "node_modules/@discoveryjs/json-ext": { @@ -754,23 +911,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", + "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -786,18 +943,18 @@ } }, "node_modules/@eslint/js": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", - "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -827,443 +984,65 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", "dev": true, "dependencies": { - "sprintf-js": "~1.0.2" + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", - "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", - "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", - "dev": true, - "dependencies": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", - "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-mock": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", - "dev": true, - "dependencies": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.4.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", - "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", - "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", - "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.5.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.25.16" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", - "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", - "dev": true, - "dependencies": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", - "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.5.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", - "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" + "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { @@ -1301,6 +1080,19 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", + "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" + }, + "node_modules/@lit/reactive-element": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1342,63 +1134,49 @@ "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", "dev": true }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.1.0.tgz", - "integrity": "sha512-w1qd368vtrwttm1PRJWPW1QHlbmHrVDGs1eBH/jZvRPUFS4MNXV9Q33EQdjOdeAxZ7O8+3wM7zxztm2nfUSyKw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true }, - "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true }, - "node_modules/@types/babel__traverse": { - "version": "7.18.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.5.tgz", - "integrity": "sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q==", + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", "dev": true, "dependencies": { - "@babel/types": "^7.3.0" + "@types/node": "*" } }, "node_modules/@types/eslint": { @@ -1427,15 +1205,6 @@ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -1460,20 +1229,16 @@ "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/jest": { - "version": "29.5.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.1.tgz", - "integrity": "sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } + "node_modules/@types/jasmine": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.5.tgz", + "integrity": "sha512-9YHUdvuNDDRJYXZwHqSsO72Ok0vmqoJbNn73ttyITQp/VA60SarnZ+MPLD37rJAhVoKp+9BWOvJP5tHIRfZylQ==", + "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "node_modules/@types/json5": { @@ -1489,9 +1254,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.1.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.7.tgz", - "integrity": "sha512-WCuw/o4GSwDGMoonES8rcvwsig77dGCMbZDrZr2x4ZZiNW4P/gcoZXe/0twgtobcTkmg9TuKflxYL/DuwDyJzg==", + "version": "20.4.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.7.tgz", + "integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==", "dev": true }, "node_modules/@types/normalize-package-data": { @@ -1500,23 +1265,16 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", "dev": true }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" }, "node_modules/@types/yargs": { "version": "17.0.24", @@ -1534,32 +1292,33 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz", - "integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz", + "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.6", - "@typescript-eslint/type-utils": "5.59.6", - "@typescript-eslint/utils": "5.59.6", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/type-utils": "6.5.0", + "@typescript-eslint/utils": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1568,25 +1327,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz", - "integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", + "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.6", - "@typescript-eslint/types": "5.59.6", - "@typescript-eslint/typescript-estree": "5.59.6", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1595,16 +1355,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz", - "integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", + "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.6", - "@typescript-eslint/visitor-keys": "5.59.6" + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1612,25 +1372,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz", - "integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz", + "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.6", - "@typescript-eslint/utils": "5.59.6", + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/utils": "6.5.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1639,12 +1399,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz", - "integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", + "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1652,21 +1412,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz", - "integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", + "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.6", - "@typescript-eslint/visitor-keys": "5.59.6", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1679,42 +1439,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz", - "integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.5.0.tgz", + "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.6", - "@typescript-eslint/types": "5.59.6", - "@typescript-eslint/typescript-estree": "5.59.6", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "semver": "^7.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz", - "integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", + "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.6", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.5.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1868,9 +1627,9 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz", - "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, "engines": { "node": ">=14.15.0" @@ -1881,9 +1640,9 @@ } }, "node_modules/@webpack-cli/info": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", - "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, "engines": { "node": ">=14.15.0" @@ -1894,9 +1653,9 @@ } }, "node_modules/@webpack-cli/serve": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz", - "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, "engines": { "node": ">=14.15.0" @@ -1923,10 +1682,23 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1953,6 +1725,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -1966,19 +1747,6 @@ "node": ">=8.9" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2044,24 +1812,24 @@ } }, "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", "dev": true, "dependencies": { - "type-fest": "^0.21.3" + "type-fest": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "engines": { "node": ">=10" @@ -2107,6 +1875,12 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2126,6 +1900,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, "node_modules/array-includes": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", @@ -2154,6 +1934,25 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", + "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", @@ -2190,29 +1989,19 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { + "node_modules/arraybuffer.prototype.slice": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, "engines": { "node": ">= 0.4" }, @@ -2220,95 +2009,70 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true, - "dependencies": { - "@jest/transform": "^29.5.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "node": ">=0.10.0" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", "dev": true, "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@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-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true }, - "node_modules/babel-preset-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/balanced-match": { @@ -2317,6 +2081,35 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2335,6 +2128,51 @@ "node": ">=8" } }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2357,10 +2195,98 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "node_modules/browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "dev": true, + "dependencies": { + "resolve": "^1.17.0" + } + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "dev": true, "funding": [ { @@ -2370,13 +2296,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" }, "bin": { "browserslist": "cli.js" @@ -2385,25 +2315,28 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "node-int64": "^0.4.0" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, "node_modules/buffer-from": { @@ -2412,6 +2345,18 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true + }, "node_modules/builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", @@ -2421,6 +2366,15 @@ "semver": "^7.0.0" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -2470,9 +2424,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001488", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz", - "integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==", + "version": "1.0.30001515", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz", + "integrity": "sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA==", "dev": true, "funding": [ { @@ -2505,15 +2459,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -2577,31 +2522,29 @@ "node": ">=8" } }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dev": true, "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate": { @@ -2663,88 +2606,233 @@ "node": ">=8" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==", + "dev": true, + "dependencies": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + } + }, + "node_modules/combine-source-map/node_modules/convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==", + "dev": true + }, + "node_modules/combine-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "dev": true + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz", + "integrity": "sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=14" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "node_modules/conventional-changelog-conventionalcommits": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-6.1.0.tgz", + "integrity": "sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==", "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "node": ">=14" } }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/conventional-commits-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", + "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "is-text-path": "^1.0.1", + "JSONStream": "^1.3.5", + "meow": "^8.1.2", + "split2": "^3.2.2" + }, + "bin": { + "conventional-commits-parser": "cli.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=14" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } }, "node_modules/cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "dependencies": { "import-fresh": "^3.2.1", @@ -2759,6 +2847,70 @@ "url": "https://github.com/sponsors/d-fischer" } }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.4.0.tgz", + "integrity": "sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==", + "dev": true, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=7", + "ts-node": ">=10", + "typescript": ">=4" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2773,25 +2925,47 @@ "node": ">= 8" } }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, "node_modules/css-functions-list": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", - "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.0.tgz", + "integrity": "sha512-d/jBMPyYybkkLVypgtGv12R+pIFw4/f/IHtCTxWpZc8ofTYOPigIgmA6vu5rMHartZC+WuXhBUHfnyNUIQSYrg==", "dev": true, "engines": { "node": ">=12.22" } }, "node_modules/css-loader": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.4.tgz", - "integrity": "sha512-0Y5uHtK5BswfaGJ+jrO+4pPg1msFBc0pwPIE1VqfpmVn6YbDfYfXMj8rfd7nt+4goAhJueO+H/I40VWJfcP1mQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.21", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.1", + "postcss-modules-local-by-default": "^4.0.3", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", @@ -2833,6 +3007,35 @@ "node": ">=4" } }, + "node_modules/currency-format": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/currency-format/-/currency-format-1.0.13.tgz", + "integrity": "sha512-mokQiLDVtf0GgGVGbPE0Epx587z0d2WhOA0ITvjSDunyZ/DexmOR5hRrK9RU7hbyWe3G6Sf7iE29YKhiGjexSQ==" + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/dargs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2884,25 +3087,22 @@ "node": ">=0.10.0" } }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/define-properties": { @@ -2921,24 +3121,67 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" } }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2963,30 +3206,81 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/domain-browser": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz", + "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, "node_modules/electron-to-chromium": { - "version": "1.4.397", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.397.tgz", - "integrity": "sha512-jwnPxhh350Q/aMatQia31KAIQdhEsYS0fFZ0BQQlN9tfvOEwShu6ZNwI4kL/xBabjcB/nTy6lSt17kNIluJZ8Q==", + "version": "1.4.457", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.457.tgz", + "integrity": "sha512-/g3UyNDmDd6ebeWapmAoiyy+Sy2HyJ+/X8KyvNeHfKRFfHaA2W8oF5fxD5F3tjBDcjpwo0iek6YNgxNXDBoEtA==", "dev": true }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -3002,10 +3296,49 @@ "node": ">= 4" } }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", + "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.7.tgz", + "integrity": "sha512-P+jDFbvK6lE3n1OL+q9KuzdOFWkkZ/cMV9gol/SbVfpyqfvrfrFTOFJ6fQm2VC3PZHlU3QPhVwmbsCnauHF2MQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz", - "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -3015,6 +3348,12 @@ "node": ">=10.13.0" } }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, "node_modules/envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", @@ -3037,18 +3376,19 @@ } }, "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", + "get-intrinsic": "^1.2.1", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", @@ -3068,14 +3408,18 @@ "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", "safe-regex-test": "^1.0.0", "string.prototype.trim": "^1.2.7", "string.prototype.trimend": "^1.0.6", "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.10" }, "engines": { "node": ">= 0.4" @@ -3130,6 +3474,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", + "dev": true + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -3139,6 +3489,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3152,27 +3508,27 @@ } }, "node_modules/eslint": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", - "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", + "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.40.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.48.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3180,22 +3536,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -3254,70 +3607,48 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", - "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "node_modules/eslint-plugin-es-x": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.2.0.tgz", + "integrity": "sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==", "dev": true, "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.6.0" }, "engines": { - "node": ">=8.10.0" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" + "eslint": ">=8" } }, "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", + "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", "dev": true, "dependencies": { "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", "array.prototype.flat": "^1.3.1", "array.prototype.flatmap": "^1.3.1", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", + "eslint-module-utils": "^2.8.0", "has": "^1.0.3", - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "is-glob": "^4.0.3", "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" }, "engines": { "node": ">=4" @@ -3348,31 +3679,31 @@ } }, "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/eslint-plugin-n": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", - "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.2.tgz", + "integrity": "sha512-Y66uDfUNbBzypsr0kELWrIz+5skicECrLUqlWuXawNSLUq3ltGlCwu6phboYYOTSnoTdHgTLrc+5Ydo6KjzZog==", "dev": true, "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", + "eslint-plugin-es-x": "^7.1.0", + "ignore": "^5.2.4", + "is-core-module": "^2.12.1", "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" + "resolve": "^1.22.2", + "semver": "^7.5.3" }, "engines": { - "node": ">=12.22.0" + "node": ">=16.0.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" @@ -3400,43 +3731,16 @@ "dev": true, "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, + "estraverse": "^4.1.1" + }, "engines": { - "node": ">=10" + "node": ">=8.0.0" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3553,9 +3857,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -3578,12 +3882,12 @@ } }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -3594,19 +3898,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -3667,6 +3958,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -3676,6 +3973,16 @@ "node": ">=0.8.x" } }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3699,30 +4006,11 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -3731,9 +4019,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3788,15 +4076,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3811,546 +4090,376 @@ }, "node_modules/fill-range": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "dependencies": { - "global-prefix": "^3.0.0" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "ms": "2.0.0" } }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "ee-first": "1.1.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "define-properties": "^1.1.3" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/globjoin": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", - "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "is-callable": "^1.1.3" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "dev": true, "dependencies": { - "function-bind": "^1.1.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">= 0.4.0" + "node": ">=14.14" } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6.9.0" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/git-raw-commits": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", + "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", "dev": true, + "dependencies": { + "dargs": "^7.0.0", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.js" + }, "engines": { - "node": ">=10.17.0" + "node": ">=10" } }, - "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "bin": { - "husky": "lib/bin.js" + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=14" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/typicode" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" + "dependencies": { + "is-glob": "^4.0.3" }, - "peerDependencies": { - "postcss": "^8.1.0" + "engines": { + "node": ">=10.13.0" } }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", "dev": true, + "dependencies": { + "ini": "^1.3.4" + }, "engines": { - "node": ">= 4" + "node": ">=4" } }, - "node_modules/immutable": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", - "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", - "dev": true - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "global-prefix": "^3.0.0" }, "engines": { "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" + "isexe": "^2.0.0" }, "bin": { - "import-local-fixture": "fixtures/cli.js" + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" }, "engines": { "node": ">=8" @@ -4359,133 +4468,126 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, "engines": { - "node": ">=0.8.19" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true, "engines": { - "node": ">=10.13.0" + "node": ">=6" } }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "function-bind": "^1.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4.0" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "get-intrinsic": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "dev": true, "engines": { "node": ">= 0.4" @@ -4494,25 +4596,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true, - "dependencies": { - "has": "^1.0.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "has-symbols": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4521,907 +4623,847 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "dependencies": { - "is-extglob": "^2.1.1" + "lru-cache": "^6.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/is-negative-zero": { + "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", "dev": true, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.8" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8.0.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10.17.0" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", "dev": true, + "bin": { + "husky": "lib/bin.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/i18next": { + "version": "23.4.6", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.4.6.tgz", + "integrity": "sha512-jBE8bui969Ygv7TVYp0pwDZB7+he0qsU+nz7EcfdqSh+QvKjEfl9YPRQd/KrGiMhTYFGkeuPaeITenKK/bSFDg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.22.5" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "dependencies": { - "isobject": "^3.0.1" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, "engines": { - "node": ">= 0.4" + "node": "^10 || ^12 || >= 14" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "dev": true, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.8.19" } }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha512-0mVWSSbNDvedDWIN4wxLsdPM4a7cIPcpyMxj3QZ406QRwQ6ePGB1YIHxVPjqpcUGbWQ5C+nHTwGNWAGvt7ggVA==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "source-map": "~0.5.3" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "node_modules/inline-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", "dev": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" }, "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">= 0.4" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "dev": true, "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", "dev": true, "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", - "import-local": "^3.0.2", - "jest-cli": "^29.5.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "has-bigints": "^1.0.1" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-changed-files": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", - "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" + "binary-extensions": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-circus": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", - "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-cli": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", - "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "dependencies": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-config": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", - "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-docblock": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-each": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", - "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-environment-node": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", - "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-haste-map": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", - "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-haste-map/node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.5.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.12.0" } }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-leak-detector": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", - "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, - "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-message-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-mock": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", - "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-util": "^29.5.0" + "isobject": "^3.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, - "peerDependencies": { - "jest-resolve": "*" + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-regex-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-resolve": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", - "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-resolve-dependencies": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", - "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" + "has-symbols": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-runner": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", - "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "node_modules/is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "text-extensions": "^1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-runner/node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", "dev": true, "dependencies": { - "@types/node": "*", - "jest-util": "^29.5.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": ">= 8.0.0" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/gjtorikian/" } }, - "node_modules/jest-runtime": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", - "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-runtime/node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/jest-snapshot": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", - "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.5.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-validate": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", - "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "leven": "^3.1.0", - "pretty-format": "^29.5.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", "dev": true, - "engines": { - "node": ">=10" + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/jest-watcher": { + "node_modules/jasmine-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.0.tgz", + "integrity": "sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ==", + "dev": true + }, + "node_modules/jest-util": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", - "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", "dev": true, "dependencies": { - "@jest/test-result": "^29.5.0", "@jest/types": "^29.5.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.5.0", - "string-length": "^4.0.1" + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5456,16 +5498,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5514,60 +5546,332 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/karma": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", + "integrity": "sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma-coverage/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/karma-typescript/-/karma-typescript-5.5.4.tgz", + "integrity": "sha512-D7nQ96xu/UekuqCmiPimnCuOFqp8+BxiND6MU6IJVN37E7DgXzr7SUeTzwuTHtKSYpgxKv4iOTUteYTxpeZL9A==", + "dev": true, + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2", + "assert": "^2.0.0", + "async": "^3.0.1", + "browser-resolve": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^5.4.3", + "combine-source-map": "^0.8.0", + "console-browserify": "^1.2.0", + "constants-browserify": "^1.0.0", + "convert-source-map": "^1.7.0", + "crypto-browserify": "^3.12.0", + "diff": "^4.0.1", + "domain-browser": "^4.16.0", + "events": "^3.2.0", + "glob": "^7.1.6", + "https-browserify": "^1.0.0", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.19", + "log4js": "^6.3.0", + "minimatch": "^3.0.4", + "os-browserify": "^0.3.0", + "pad": "^3.2.0", + "path-browserify": "^1.0.0", + "process": "^0.11.10", + "punycode": "^2.1.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^3.1.1", + "source-map": "^0.7.3", + "stream-browserify": "^3.0.0", + "stream-http": "^3.1.0", + "string_decoder": "^1.3.0", + "timers-browserify": "^2.0.11", + "tmp": "^0.2.1", + "tty-browserify": "^0.0.1", + "url": "^0.11.0", + "util": "^0.12.1", + "vm-browserify": "^1.1.2" + }, + "peerDependencies": { + "karma": "1 || 2 || 3 || 4 || 5 || 6", + "typescript": "1 || 2 || 3 || 4 || 5" + } + }, + "node_modules/karma-typescript/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/karma/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/karma/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "minimist": "^1.2.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "bin": { - "json5": "lib/cli.js" + "engines": { + "node": ">=8" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, "node_modules/known-css-properties": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz", - "integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz", + "integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==", "dev": true }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5597,39 +5901,36 @@ "dev": true }, "node_modules/lint-staged": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.2.tgz", - "integrity": "sha512-71gSwXKy649VrSU09s10uAT0rWCcY3aewhMaHyl2N84oBk4Xs9HgxvUp3AYu+bNsK4NrOYYxvSgg7FyGJ+jGcA==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-14.0.1.tgz", + "integrity": "sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==", "dev": true, "dependencies": { - "chalk": "5.2.0", - "cli-truncate": "^3.1.0", - "commander": "^10.0.0", - "debug": "^4.3.4", - "execa": "^7.0.0", + "chalk": "5.3.0", + "commander": "11.0.0", + "debug": "4.3.4", + "execa": "7.2.0", "lilconfig": "2.1.0", - "listr2": "^5.0.7", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.3", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.2.2" + "listr2": "6.6.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.1" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": "^16.14.0 || >=18.0.0" }, "funding": { "url": "https://opencollective.com/lint-staged" } }, "node_modules/lint-staged/node_modules/chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -5639,18 +5940,18 @@ } }, "node_modules/lint-staged/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", "dev": true, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/lint-staged/node_modules/execa": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", - "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", @@ -5758,22 +6059,20 @@ } }, "node_modules/listr2": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", - "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", + "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", "dev": true, "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.19", - "log-update": "^4.0.0", - "p-map": "^4.0.0", + "cli-truncate": "^3.1.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^5.0.1", "rfdc": "^1.3.0", - "rxjs": "^7.8.0", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^8.1.0" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=16.0.0" }, "peerDependencies": { "enquirer": ">= 2.3.0 < 3" @@ -5784,63 +6083,94 @@ } } }, - "node_modules/listr2/node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/listr2/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/listr2/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/listr2/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lit": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.3.tgz", + "integrity": "sha512-l4slfspEsnCcHVRTvaP7YnkTZEZggNFywLEIhQaGhYDczG+tu/vlgm/KaWIEjIp+ZyV20r2JnZctMb8LeLCG7Q==", + "dependencies": { + "@lit/reactive-element": "^2.0.4", + "lit-element": "^4.0.4", + "lit-html": "^3.1.2" + } + }, + "node_modules/lit-element": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.5.tgz", + "integrity": "sha512-iTWskWZEtn9SyEf4aBG6rKT8GABZMrTWop1+jopsEOgEcugcXJGKuX5bEbkq9qfzY+XB4MAgCaSPwnNpdsNQ3Q==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.0.4", + "lit-html": "^3.1.2" + } + }, + "node_modules/lit-html": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.3.tgz", + "integrity": "sha512-FwIbqDD8O/8lM4vUZ4KvQZjPPNx7V1VhT7vmRB8RBAO0AU6wuTVdoXiu2CivVjEGdugvcbPNBLtPE1y0ifplHA==", + "dependencies": { + "@types/trusted-types": "^2.0.2" } }, "node_modules/loader-runner": { @@ -5893,10 +6223,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true + }, "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==", "dev": true }, "node_modules/lodash.merge": { @@ -5905,88 +6265,131 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true + }, "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", + "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", "dev": true, "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-escapes": "^5.0.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^5.0.0", + "strip-ansi": "^7.0.1", + "wrap-ansi": "^8.0.1" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" }, "engines": { - "node": ">=8" + "node": ">=8.0" } }, "node_modules/lru-cache": { @@ -6017,9 +6420,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -6031,15 +6434,6 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -6062,21 +6456,40 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", @@ -6135,12 +6548,43 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">=8.6" + "node": ">=4.0.0" } }, "node_modules/mime-db": { @@ -6254,6 +6698,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -6289,6 +6745,18 @@ "node": ">= 6" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6319,11 +6787,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } }, "node_modules/neo-async": { "version": "2.6.2", @@ -6331,16 +6802,10 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "node_modules/normalize-package-data": { @@ -6379,6 +6844,15 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -6388,6 +6862,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -6415,6 +6905,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, "node_modules/object.values": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", @@ -6432,6 +6951,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6457,22 +6988,28 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6503,21 +7040,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -6527,6 +7049,24 @@ "node": ">=6" } }, + "node_modules/pad": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pad/-/pad-3.2.0.tgz", + "integrity": "sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==", + "dev": true, + "dependencies": { + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6539,6 +7079,19 @@ "node": ">=6" } }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -6557,6 +7110,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6599,6 +7167,22 @@ "node": ">=8" } }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -6629,15 +7213,6 @@ "node": ">=0.10" } }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -6703,9 +7278,9 @@ } }, "node_modules/postcss": { - "version": "8.4.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", - "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "version": "8.4.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", + "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", "dev": true, "funding": [ { @@ -6749,9 +7324,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.2.tgz", - "integrity": "sha512-mR/pcIsQhU2UgKYOPjRCSgacmjn08pyrHk+Vrm8WEKjDWgqO43vdRkzmxyZOZWiKr6Ed9gpReQHhLUGVAcn9jw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -6877,59 +7452,49 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz", + "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.4.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 0.6.0" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dev": true, "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" } }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -6939,21 +7504,38 @@ "node": ">=6" } }, - "node_modules/pure-rand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", - "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ] + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -6993,11 +7575,59 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } }, "node_modules/read-pkg": { "version": "5.2.0", @@ -7111,9 +7741,9 @@ } }, "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -7128,6 +7758,20 @@ "node": ">=8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7165,6 +7809,17 @@ "node": ">=8" } }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, "node_modules/regex-parser": { "version": "2.2.11", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", @@ -7188,18 +7843,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7218,6 +7861,12 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -7247,7 +7896,7 @@ "node": ">=8" } }, - "node_modules/resolve-cwd/node_modules/resolve-from": { + "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", @@ -7256,13 +7905,16 @@ "node": ">=8" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/resolve-global": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", + "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", "dev": true, + "dependencies": { + "global-dirs": "^0.1.1" + }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/resolve-url-loader": { @@ -7270,43 +7922,31 @@ "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", "dev": true, - "dependencies": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.14", - "source-map": "0.6.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/resolve-url-loader/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dev": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/reusify": { @@ -7340,6 +7980,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7363,21 +8013,24 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", "dev": true, "dependencies": { - "tslib": "^2.1.0" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.1.tgz", - "integrity": "sha512-KaI6gPil5m9vF7DKaoXxx1ia9fxS4qG5YveErRRVknPDXXriu5M8h48YRjB6h5ZUOKuAKlSJYb0GaDe8I39fRw==", - "dev": true - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7412,10 +8065,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "node_modules/sass": { - "version": "1.62.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", - "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==", + "version": "1.66.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz", + "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -7430,12 +8089,11 @@ } }, "node_modules/sass-loader": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.0.tgz", - "integrity": "sha512-LeWNswSEujsZnwdn9AuA+Q5wZEAFlU+eORQsDKp35OtGAfFxYxpfk/Ylon+TGGkazSqxi2EHDTqdr3di8r7nCg==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", + "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", "dev": true, "dependencies": { - "klona": "^2.0.6", "neo-async": "^2.6.2" }, "engines": { @@ -7468,9 +8126,9 @@ } }, "node_modules/schema-utils": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", - "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -7486,9 +8144,9 @@ } }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -7501,14 +8159,39 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -7562,12 +8245,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -7605,6 +8282,45 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/socket.io": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.2.tgz", + "integrity": "sha512-Vp+lSks5k0dewYTfwgPT9UeGGd+ht7sCpB7p0e83VgO4X/AHYWhXITMrNk/pg8syY2bpx23ptClCQuHhqi2BgQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7665,31 +8381,90 @@ "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", "dev": true }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "dependencies": { + "readable-stream": "^3.0.0" + } }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, "dependencies": { - "escape-string-regexp": "^2.0.0" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" }, "engines": { - "node": ">=10" + "node": ">=8.0" } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, "engines": { - "node": ">=8" + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" } }, "node_modules/string-argv": { @@ -7701,19 +8476,6 @@ "node": ">=0.6.19" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -7744,9 +8506,9 @@ } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { "ansi-regex": "^6.0.1" @@ -7864,22 +8626,22 @@ "dev": true }, "node_modules/stylelint": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.6.2.tgz", - "integrity": "sha512-fjQWwcdUye4DU+0oIxNGwawIPC5DvG5kdObY5Sg4rc87untze3gC/5g/ikePqVjrAsBUZjwMN+pZsAYbDO6ArQ==", + "version": "15.10.3", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.10.3.tgz", + "integrity": "sha512-aBQMMxYvFzJJwkmg+BUUg3YfPyeuCuKo2f+LOw7yYbU8AZMblibwzp9OV4srHVeQldxvSFdz0/Xu8blq2AesiA==", "dev": true, "dependencies": { - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1", - "@csstools/media-query-list-parser": "^2.0.4", - "@csstools/selector-specificity": "^2.2.0", + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0", + "@csstools/media-query-list-parser": "^2.1.4", + "@csstools/selector-specificity": "^3.0.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^8.1.3", - "css-functions-list": "^3.1.0", + "cosmiconfig": "^8.2.0", + "css-functions-list": "^3.2.0", "css-tree": "^2.3.1", "debug": "^4.3.4", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.1", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^6.0.1", "global-modules": "^2.0.0", @@ -7890,17 +8652,16 @@ "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.27.0", + "known-css-properties": "^0.28.0", "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", + "meow": "^10.1.5", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-media-query-parser": "^0.2.3", + "postcss": "^8.4.27", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.12", + "postcss-selector-parser": "^6.0.13", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", @@ -7909,11 +8670,10 @@ "supports-hyperlinks": "^3.0.0", "svg-tags": "^1.0.0", "table": "^6.8.1", - "v8-compile-cache": "^2.3.0", "write-file-atomic": "^5.0.1" }, "bin": { - "stylelint": "bin/stylelint.js" + "stylelint": "bin/stylelint.mjs" }, "engines": { "node": "^14.13.1 || >=16.0.0" @@ -8033,12 +8793,66 @@ "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", "dev": true }, + "node_modules/stylelint/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/camelcase-keys": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", + "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", + "dev": true, + "dependencies": { + "camelcase": "^6.3.0", + "map-obj": "^4.1.0", + "quick-lru": "^5.1.1", + "type-fest": "^1.2.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/decamelize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stylelint/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/stylelint/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stylelint/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -8057,13 +8871,93 @@ "node": ">=0.10.0" } }, - "node_modules/stylelint/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/stylelint/node_modules/meow": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", + "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", "dev": true, + "dependencies": { + "@types/minimist": "^1.2.2", + "camelcase-keys": "^7.0.0", + "decamelize": "^5.0.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.2", + "read-pkg-up": "^8.0.0", + "redent": "^4.0.0", + "trim-newlines": "^4.0.2", + "type-fest": "^1.2.2", + "yargs-parser": "^20.2.9" + }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/read-pkg": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", + "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/read-pkg-up": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", + "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0", + "read-pkg": "^6.0.0", + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/redent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", + "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", + "dev": true, + "dependencies": { + "indent-string": "^5.0.0", + "strip-indent": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/stylelint/node_modules/signal-exit": { @@ -8078,18 +8972,57 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/stylelint/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/stylelint/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/strip-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/trim-newlines": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", + "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/stylelint/node_modules/write-file-atomic": { @@ -8105,6 +9038,15 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/stylelint/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8293,18 +9235,13 @@ } } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "node_modules/text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, "engines": { - "node": ">=8" + "node": ">=0.10" } }, "node_modules/text-table": { @@ -8319,11 +9256,38 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } }, "node_modules/to-fast-properties": { "version": "2.0.0", @@ -8346,6 +9310,15 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -8355,65 +9328,22 @@ "node": ">=8" } }, - "node_modules/ts-jest": { - "version": "29.1.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz", - "integrity": "sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==", + "node_modules/ts-api-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", + "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=16.13.0" }, "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" + "typescript": ">=4.2.0" } }, "node_modules/ts-loader": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", - "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -8429,6 +9359,49 @@ "webpack": "^5.0.0" } }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -8444,24 +9417,25 @@ "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, + "node_modules/tsyringe": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.8.0.tgz", + "integrity": "sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==", "dependencies": { - "tslib": "^1.8.1" + "tslib": "^1.9.3" }, "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "node": ">= 6.0.0" } }, + "node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8474,15 +9448,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -8495,6 +9460,70 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -8510,16 +9539,35 @@ } }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", + "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" } }, "node_modules/unbox-primitive": { @@ -8537,6 +9585,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -8576,36 +9642,54 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", + "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", + "dev": true, + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, "engines": { - "node": ">=10.12.0" + "node": ">= 0.4.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, "node_modules/validate-npm-package-license": { @@ -8618,13 +9702,28 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, - "dependencies": { - "makeerror": "1.0.12" + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/watchpack": { @@ -8640,10 +9739,19 @@ "node": ">=10.13.0" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/webpack": { - "version": "5.82.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.1.tgz", - "integrity": "sha512-C6uiGQJ+Gt4RyHXXYt+v9f+SN1v83x68URwgxNQ98cvH8kxiuywWGP4XeNZ1paOzZ63aY3cTciCEQJNFUljlLw==", + "version": "5.88.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", + "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -8652,10 +9760,10 @@ "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", + "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.14.0", + "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -8665,7 +9773,7 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", @@ -8688,15 +9796,15 @@ } }, "node_modules/webpack-cli": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz", - "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.0", - "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.4", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", "commander": "^10.0.1", "cross-spawn": "^7.0.3", @@ -8795,17 +9903,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -8820,15 +9927,6 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -8881,17 +9979,34 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=0.4" } }, "node_modules/y18n": { @@ -8910,9 +10025,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", "dev": true, "engines": { "node": ">= 14" @@ -8974,6 +10089,15 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index e849c1e7..75f4a0f5 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,23 @@ { "name": "@xsolla/pay-station-sdk", - "version": "0.0.1", + "version": "0.0.22", "description": "Pay Station SDK", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "dist/main.js", + "types": "dist/index.d.ts", "engines": { "node": ">=16" }, "scripts": { - "build": "webpack --mode=production && tsc", + "build": "webpack --mode=production", "dev": "webpack --mode=development --watch", - "test": "jest", - "test:dev": "jest --watch", - "lint": "eslint \"src/**/*.{ts,tsx}\" && stylelint ./styles/**/*.scss", + "test": "karma start --single-run", + "test:dev": "karma start --auto-watch", + "lint": "npm run eslint:lint && npm run stylelint:lint", + "eslint:lint": "eslint \"src/**/*.{ts,tsx}\"", + "stylelint:lint": "stylelint \"src/**/*.scss\"", "prepare": "husky install", "lib-publish": "npm run build && npm test && npm run lint && npm publish --access public", - "prettier": "prettier --write ." + "prettier": "prettier --write ./src" }, "lint-staged": { "**/*": "prettier --write --ignore-unknown" @@ -27,32 +29,44 @@ "lib/**/*" ], "devDependencies": { - "@types/jest": "^29.5.1", - "@types/prettier": "^2.7.2", - "@typescript-eslint/eslint-plugin": "^5.59.6", - "@typescript-eslint/parser": "^5.59.6", - "css-loader": "^6.7.4", - "eslint": "^8.40.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.7.0", + "@commitlint/cli": "^17.7.1", + "@commitlint/config-conventional": "^17.7.0", + "@types/jasmine": "^4.3.5", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", + "css-loader": "^6.8.1", + "eslint": "^8.48.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-n": "^16.0.2", "eslint-plugin-promise": "^6.1.1", "eslint-webpack-plugin": "^4.0.1", "husky": "^8.0.3", - "jest": "^29.5.0", - "lint-staged": "^13.2.2", + "karma": "^6.4.2", + "karma-chrome-launcher": "^3.2.0", + "karma-coverage": "^2.2.1", + "karma-jasmine": "^5.1.0", + "karma-typescript": "^5.5.4", + "lint-staged": "^14.0.1", "mini-css-extract-plugin": "^2.7.6", - "prettier": "2.8.8", + "prettier": "3.0.2", + "raw-loader": "^4.0.2", + "reflect-metadata": "^0.1.13", "resolve-url-loader": "^5.0.0", - "sass": "^1.62.1", - "sass-loader": "^13.3.0", - "stylelint": "^15.6.2", + "sass": "^1.66.1", + "sass-loader": "^13.3.2", + "stylelint": "^15.10.3", "stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-standard-scss": "^9.0.0", "stylelint-order": "^6.0.3", - "ts-jest": "^29.1.0", - "ts-loader": "^9.4.2", - "typescript": "^5.0.4", - "webpack": "^5.82.1", - "webpack-cli": "^5.1.1" + "ts-loader": "^9.4.4", + "typescript": "^5.2.2", + "webpack": "^5.82.2", + "webpack-cli": "^5.1.4" + }, + "dependencies": { + "currency-format": "^1.0.13", + "i18next": "^23.4.6", + "lit": "^3.1.3", + "tsyringe": "^4.8.0" } } diff --git a/readme_images/client_web_app.png b/readme_images/client_web_app.png new file mode 100644 index 00000000..0c128bca Binary files /dev/null and b/readme_images/client_web_app.png differ diff --git a/readme_images/component_state_sync.png b/readme_images/component_state_sync.png new file mode 100644 index 00000000..8eb353f0 Binary files /dev/null and b/readme_images/component_state_sync.png differ diff --git a/readme_images/customizing_secure_components.png b/readme_images/customizing_secure_components.png new file mode 100644 index 00000000..0626bd14 Binary files /dev/null and b/readme_images/customizing_secure_components.png differ diff --git a/readme_images/general_integration_scheme.png b/readme_images/general_integration_scheme.png new file mode 100644 index 00000000..bae70449 Binary files /dev/null and b/readme_images/general_integration_scheme.png differ diff --git a/readme_images/sdk_web_components_scheme.png b/readme_images/sdk_web_components_scheme.png new file mode 100644 index 00000000..a9c97fba Binary files /dev/null and b/readme_images/sdk_web_components_scheme.png differ diff --git a/readme_images/secure_component_scheme.png b/readme_images/secure_component_scheme.png new file mode 100644 index 00000000..21da46e1 Binary files /dev/null and b/readme_images/secure_component_scheme.png differ diff --git a/src/core/actions/action.interface.ts b/src/core/actions/action.interface.ts new file mode 100644 index 00000000..3cfe33a6 --- /dev/null +++ b/src/core/actions/action.interface.ts @@ -0,0 +1,4 @@ +export interface Action { + type: T; + data: D; +} diff --git a/src/core/actions/check-status.action.type.ts b/src/core/actions/check-status.action.type.ts new file mode 100644 index 00000000..c5574774 --- /dev/null +++ b/src/core/actions/check-status.action.type.ts @@ -0,0 +1,5 @@ +import { Action } from './action.interface'; + +export type CheckStatusActionType = 'check_status'; + +export type CheckStatusAction = Action; diff --git a/src/core/actions/hide-form.action.type.ts b/src/core/actions/hide-form.action.type.ts new file mode 100644 index 00000000..b666d1a3 --- /dev/null +++ b/src/core/actions/hide-form.action.type.ts @@ -0,0 +1,5 @@ +import { Action } from './action.interface'; + +export type HideFormActionType = 'hide_form'; + +export type HideFormAction = Action; diff --git a/src/core/actions/is-show-fields-action.function.ts b/src/core/actions/is-show-fields-action.function.ts new file mode 100644 index 00000000..23210fa2 --- /dev/null +++ b/src/core/actions/is-show-fields-action.function.ts @@ -0,0 +1,8 @@ +import { Action } from './action.interface'; +import { ShowFieldsAction } from './show-fields.action.type'; + +export const isShowFieldsAction = ( + value: Action, +): value is ShowFieldsAction => { + return value.type === 'show_fields'; +}; diff --git a/src/core/actions/is-status-updated-action.function.ts b/src/core/actions/is-status-updated-action.function.ts new file mode 100644 index 00000000..74f8bfd2 --- /dev/null +++ b/src/core/actions/is-status-updated-action.function.ts @@ -0,0 +1,8 @@ +import { Action } from './action.interface'; +import { StatusUpdatedAction } from './status-updated.action.type'; + +export const isStatusUpdatedAction = ( + value: Action +): value is StatusUpdatedAction => { + return value.type === 'status_updated'; +}; diff --git a/src/core/actions/next-action-type.enum.ts b/src/core/actions/next-action-type.enum.ts new file mode 100644 index 00000000..b5ca5664 --- /dev/null +++ b/src/core/actions/next-action-type.enum.ts @@ -0,0 +1,11 @@ +export enum NextActionType { + checkStatus = 'check_status', + showFields = 'show_fields', + statusUpdated = 'status_updated', + showErrors = 'show_errors', + redirect = 'redirect', + threeDS = '3DS', + specialButton = 'special_button', + showQrCode = 'show_qr_code', + showMobilePaymentScreen = 'show_mobile_payment_screen', +} diff --git a/src/core/actions/next-action.interface.ts b/src/core/actions/next-action.interface.ts new file mode 100644 index 00000000..7838dba8 --- /dev/null +++ b/src/core/actions/next-action.interface.ts @@ -0,0 +1,22 @@ +import { CheckStatusAction } from './check-status.action.type'; +import { RedirectAction } from './redirect/redirect.action.type'; +import { ShowErrorsAction } from './show-errors.action.type'; +import { ShowFieldsAction } from './show-fields.action.type'; +import { StatusUpdatedAction } from './status-updated.action.type'; +import { ThreeDsAction } from './three-ds/three-ds.action.type'; +import { SpecialButtonAction } from './special-button.action.type'; +import { ShowQrCodeAction } from './show-qr-code.action.type'; +import { ShowMobilePaymentScreenAction } from './show-mobile-payment-screen.action.type'; +import { HideFormAction } from './hide-form.action.type'; + +export type NextAction = + | CheckStatusAction + | ShowFieldsAction + | StatusUpdatedAction + | ShowErrorsAction + | RedirectAction + | ThreeDsAction + | SpecialButtonAction + | ShowQrCodeAction + | ShowMobilePaymentScreenAction + | HideFormAction; diff --git a/src/core/actions/redirect/redirect.action.type.ts b/src/core/actions/redirect/redirect.action.type.ts new file mode 100644 index 00000000..64820e90 --- /dev/null +++ b/src/core/actions/redirect/redirect.action.type.ts @@ -0,0 +1,20 @@ +import { Field } from '../../form/field.interface'; +import { FormError } from '../../form/form-error.interface'; +import { FormMessage } from '../../form/form-message.interface'; +import { Action } from '../action.interface'; + +export type RedirectActionType = 'redirect'; + +export interface RedirectActionData { + fields: Field[]; + errors: FormError[] | null; + messages: FormMessage[] | undefined; + invoiceId?: string; + order?: object; + redirect: { + redirectUrl: string; + data: object; + }; +} + +export type RedirectAction = Action; diff --git a/src/core/actions/save-payment-method-status.enum.ts b/src/core/actions/save-payment-method-status.enum.ts new file mode 100644 index 00000000..4ac1cfb2 --- /dev/null +++ b/src/core/actions/save-payment-method-status.enum.ts @@ -0,0 +1,4 @@ +export enum SavePaymentMethodStatus { + success = 'success', + failed = 'failed', +} diff --git a/src/core/actions/show-errors.action.type.ts b/src/core/actions/show-errors.action.type.ts new file mode 100644 index 00000000..26632cba --- /dev/null +++ b/src/core/actions/show-errors.action.type.ts @@ -0,0 +1,13 @@ +import { FormError } from '../form/form-error.interface'; +import { Action } from './action.interface'; + +export type ShowErrorsActionType = 'show_errors'; + +export interface ShowErrorsActionData { + errors: FormError[] | null; +} + +export type ShowErrorsAction = Action< + ShowErrorsActionType, + ShowErrorsActionData +>; diff --git a/src/core/actions/show-fields.action.type.ts b/src/core/actions/show-fields.action.type.ts new file mode 100644 index 00000000..7d911905 --- /dev/null +++ b/src/core/actions/show-fields.action.type.ts @@ -0,0 +1,16 @@ +import { Action } from './action.interface'; +import { Field } from '../form/field.interface'; +import { FormMessage } from '../form/form-message.interface'; +import { FormError } from '../form/form-error.interface'; + +export type ShowFieldsActionType = 'show_fields'; +export interface ShowFieldsActionData { + fields: Field[]; + errors: FormError[] | undefined; + messages: FormMessage[] | null; + order?: object; +} +export type ShowFieldsAction = Action< + ShowFieldsActionType, + ShowFieldsActionData +>; diff --git a/src/core/actions/show-mobile-payment-screen.action.type.ts b/src/core/actions/show-mobile-payment-screen.action.type.ts new file mode 100644 index 00000000..ae3f05d5 --- /dev/null +++ b/src/core/actions/show-mobile-payment-screen.action.type.ts @@ -0,0 +1,12 @@ +import { Action } from './action.interface'; + +export type ShowMobilePaymentScreenActionType = 'show_mobile_payment_screen'; + +export interface ShowMobilePaymentScreenActionData { + submitButtonText: string; +} + +export type ShowMobilePaymentScreenAction = Action< + ShowMobilePaymentScreenActionType, + ShowMobilePaymentScreenActionData +>; diff --git a/src/core/actions/show-qr-code.action.type.ts b/src/core/actions/show-qr-code.action.type.ts new file mode 100644 index 00000000..2a691686 --- /dev/null +++ b/src/core/actions/show-qr-code.action.type.ts @@ -0,0 +1,14 @@ +import { Action } from './action.interface'; +import { Field } from '../form/field.interface'; + +export type ShowQrCodeActionType = 'show_qr_code'; + +export interface ShowQrCodeActionData { + fields: Field[]; + submitButtonText: string; +} + +export type ShowQrCodeAction = Action< + ShowQrCodeActionType, + ShowQrCodeActionData +>; diff --git a/src/core/actions/special-button.action.type.ts b/src/core/actions/special-button.action.type.ts new file mode 100644 index 00000000..116f851f --- /dev/null +++ b/src/core/actions/special-button.action.type.ts @@ -0,0 +1,16 @@ +import { Action } from './action.interface'; + +export type SpecialButtonActionType = 'special_button'; + +export enum SpecialButtonName { + googlePay = 'google-pay', +} + +export interface SpecialButtonActionData { + buttonName: SpecialButtonName; +} + +export type SpecialButtonAction = Action< + SpecialButtonActionType, + SpecialButtonActionData +>; diff --git a/src/core/actions/status-updated.action.type.ts b/src/core/actions/status-updated.action.type.ts new file mode 100644 index 00000000..3070f525 --- /dev/null +++ b/src/core/actions/status-updated.action.type.ts @@ -0,0 +1,20 @@ +import { Action } from './action.interface'; +import { StatusEnum } from '../status/status.enum'; +import { SavePaymentMethodStatus } from './save-payment-method-status.enum'; + +export type StatusUpdatedActionType = 'status_updated'; + +export interface StatusUpdatedActionData { + statusState: StatusEnum; + group: string; + isCanceled?: boolean; + isSuccess?: boolean; + invoice?: number; + isSavePaymentMethodMode?: boolean; + savePaymentMethodStatus?: SavePaymentMethodStatus; +} + +export type StatusUpdatedAction = Action< + StatusUpdatedActionType, + StatusUpdatedActionData +>; diff --git a/src/core/actions/three-ds/checkout-form.interface.ts b/src/core/actions/three-ds/checkout-form.interface.ts new file mode 100644 index 00000000..11fe04cd --- /dev/null +++ b/src/core/actions/three-ds/checkout-form.interface.ts @@ -0,0 +1,5 @@ +export interface CheckoutForm { + method: string; + action: string; + params?: { [key: string]: string }; +} diff --git a/src/core/actions/three-ds/is-three-ds-action.function.ts b/src/core/actions/three-ds/is-three-ds-action.function.ts new file mode 100644 index 00000000..1755fab5 --- /dev/null +++ b/src/core/actions/three-ds/is-three-ds-action.function.ts @@ -0,0 +1,8 @@ +import { Action } from '../action.interface'; +import { ThreeDsAction } from './three-ds.action.type'; + +export const isThreeDsAction = ( + value: Action, +): value is ThreeDsAction => { + return value.type === '3DS'; +}; diff --git a/src/core/actions/three-ds/three-ds.action.type.ts b/src/core/actions/three-ds/three-ds.action.type.ts new file mode 100644 index 00000000..7e9cbef0 --- /dev/null +++ b/src/core/actions/three-ds/three-ds.action.type.ts @@ -0,0 +1,11 @@ +import { Action } from '../action.interface'; +import { CheckoutForm } from './checkout-form.interface'; + +export type ThreeDsActionType = '3DS'; + +export interface ThreeDsActionData { + type: 'challenge'; + data: CheckoutForm; +} + +export type ThreeDsAction = Action; diff --git a/src/core/balance.interface.ts b/src/core/balance.interface.ts new file mode 100644 index 00000000..37879aac --- /dev/null +++ b/src/core/balance.interface.ts @@ -0,0 +1,4 @@ +export interface Balance { + amount: number; + currency: string; +} diff --git a/src/core/cards/credit-card-types.ts b/src/core/cards/credit-card-types.ts new file mode 100644 index 00000000..6360ae45 --- /dev/null +++ b/src/core/cards/credit-card-types.ts @@ -0,0 +1,18 @@ +export enum CreditCardTypes { + DEFAULT = '0', + VISA = '1', + MASTERCARD = '2', + MAESTRO = '3', + AMERICAN_EXPRESS = '4', + DINERS_CLUBS = '5', + DISCOVER = '6', + JCB = '9', + DANKORT = '11', + HIPERCARD = '12', + ELO = '13', + AURA = '14', + NARANJA = '15', + MIR = '16', + CHINA_UNION_PAY = '17', + POSTEPAY = '18', +} diff --git a/src/core/combined-payment-methods.interface.ts b/src/core/combined-payment-methods.interface.ts new file mode 100644 index 00000000..a1369025 --- /dev/null +++ b/src/core/combined-payment-methods.interface.ts @@ -0,0 +1,10 @@ +import { PaymentMethod } from './payment-method.interface'; +import { SavedMethod } from './saved-method.interface'; + +export interface CombinedPaymentMethods { + paymentMethods: { + quick: PaymentMethod[]; + remained: PaymentMethod[]; + }; + savedMethods: SavedMethod[]; +} diff --git a/src/core/country-response.interface.ts b/src/core/country-response.interface.ts new file mode 100644 index 00000000..1d2d23c9 --- /dev/null +++ b/src/core/country-response.interface.ts @@ -0,0 +1,5 @@ +import { Country } from './country.interface'; + +export interface CountryResponse { + countryList: Country[]; +} diff --git a/src/core/country.interface.ts b/src/core/country.interface.ts new file mode 100644 index 00000000..fb648365 --- /dev/null +++ b/src/core/country.interface.ts @@ -0,0 +1,5 @@ +export interface Country { + ISO: string; + aliases: null | string; + name: string; +} diff --git a/src/core/country/country-code.enum.ts b/src/core/country/country-code.enum.ts new file mode 100644 index 00000000..a96c5cb8 --- /dev/null +++ b/src/core/country/country-code.enum.ts @@ -0,0 +1,5 @@ +export enum CountryCode { + Croatia = 'HR', + Ghana = 'GH', + India = 'IN', +} diff --git a/src/core/currency/currency.enum.ts b/src/core/currency/currency.enum.ts new file mode 100644 index 00000000..16d20dcd --- /dev/null +++ b/src/core/currency/currency.enum.ts @@ -0,0 +1,4 @@ +export enum Currency { + USD = 'USD', + EUR = 'EUR', +} diff --git a/src/core/customization/themes-loader.ts b/src/core/customization/themes-loader.ts new file mode 100644 index 00000000..308c7865 --- /dev/null +++ b/src/core/customization/themes-loader.ts @@ -0,0 +1,11 @@ +import 'reflect-metadata'; +import { injectable } from 'tsyringe'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import { default as styles } from '../../styles/themes/default/secure-styles/style.string-style.scss'; +@injectable() +export class ThemesLoader { + public getTheme(): string { + return styles as string; + } +} diff --git a/src/core/customization/themes.type.ts b/src/core/customization/themes.type.ts new file mode 100644 index 00000000..c2551c78 --- /dev/null +++ b/src/core/customization/themes.type.ts @@ -0,0 +1 @@ +export type Themes = 'default'; diff --git a/src/core/event-name.enum.ts b/src/core/event-name.enum.ts new file mode 100644 index 00000000..cbf3c2d7 --- /dev/null +++ b/src/core/event-name.enum.ts @@ -0,0 +1,37 @@ +export const enum EventName { + isReady = 'isReady', + error = 'error', + initPayment = 'initPayment', + initForm = 'initForm', + submitForm = 'submitForm', + getCountryList = 'getCountryList', + getPaymentMethodsList = 'getPaymentMethodsList', + getPaymentQuickMethods = 'getPaymentQuickMethods', + getCombinedPaymentMethods = 'getCombinedPaymentMethods', + getSavedMethods = 'getSavedMethods', + getUserBalance = 'getUserBalance', + getUserBalanceValue = 'getUserBalanceValue', + getLegalComponentConfig = 'getLegalComponentConfig', + getPaymentStatus = 'getPaymentStatus', + requiredComponentPing = 'requiredComponentPing', + requiredComponentPong = 'requiredComponentPong', + financeDetails = 'financeDetails', + nextAction = 'nextAction', + warning = 'warning', + getControlComponentConfig = 'getControlComponentConfig', + setSecureComponentStyles = 'setSecureComponentStyles', + formFieldsStatusChanged = 'formFieldsStatusChanged', + updateCreditCardType = 'updateCreditCardType', + publicControlChangeState = 'publicControlChangeState', + publicControlOnValueChanges = 'publicControlOnValueChanges', + formMessagesChanged = 'formMessagesChanged', + savedMethodSelected = 'savedMethodSelected', + deleteSavedMethod = 'deleteSavedMethod', + deletedSavedMethodStatus = 'deletedSavedMethodStatus', + finishLoadComponent = 'finishLoadComponent', + getFormStatus = 'getFormStatus', + applePayError = 'applePayError', + openApplePayPage = 'openApplePayPage', + submitApplePayForm = 'submitApplePayForm', + userCountryChanged = 'userCountryChanged', +} diff --git a/src/core/finance-details/cart-item.interface.ts b/src/core/finance-details/cart-item.interface.ts new file mode 100644 index 00000000..34d25955 --- /dev/null +++ b/src/core/finance-details/cart-item.interface.ts @@ -0,0 +1,15 @@ +import { CartLine } from './cart-line.interface'; +import { Price } from './price.interface'; + +export interface CartItem { + key?: string; + imgSrc?: string; + hasDefaultImg?: boolean; + title: string; + price: Price; + priceBeforeDiscount?: Price; + description?: string | null; + tax?: CartLine | null; + quantity?: number; + isBonus: boolean; +} diff --git a/src/core/finance-details/cart-line.interface.ts b/src/core/finance-details/cart-line.interface.ts new file mode 100644 index 00000000..69e1f4c0 --- /dev/null +++ b/src/core/finance-details/cart-line.interface.ts @@ -0,0 +1,10 @@ +import { Price } from './price.interface'; + +export interface CartLine { + key?: string; + title?: string; + content?: string; + money?: Price; + rate?: number; + isDateLine?: boolean; +} diff --git a/src/core/finance-details/cart-summary.interface.ts b/src/core/finance-details/cart-summary.interface.ts new file mode 100644 index 00000000..ebb59c0c --- /dev/null +++ b/src/core/finance-details/cart-summary.interface.ts @@ -0,0 +1,11 @@ +import { CartLine } from './cart-line.interface'; + +export interface CartSummary { + transactionDetails?: CartLine[]; + shipping?: CartLine[]; + subtotal?: CartLine; + subtotalPayment?: CartLine; + subtotalDetails?: CartLine[]; + total: CartLine; + totalDetails?: CartLine[]; +} diff --git a/src/core/finance-details/checkout-item.interface.ts b/src/core/finance-details/checkout-item.interface.ts new file mode 100644 index 00000000..db442ba0 --- /dev/null +++ b/src/core/finance-details/checkout-item.interface.ts @@ -0,0 +1,11 @@ +export interface CheckoutItem { + quantity: number; + amount: number | null; + amount_before_discount?: number | null; + name: string; + image_url: string; + description: string | null; + currency: string; + is_bonus: boolean; + indirect_tax_rate: number; +} diff --git a/src/core/finance-details/finance-details.interface.ts b/src/core/finance-details/finance-details.interface.ts new file mode 100644 index 00000000..312bb5a8 --- /dev/null +++ b/src/core/finance-details/finance-details.interface.ts @@ -0,0 +1,12 @@ +import { CartItem } from './cart-item.interface'; +import { CartSummary } from './cart-summary.interface'; +import { XpsFinance } from './xps-finance.interface'; +import { XpsPurchase } from './xps-purchase.interface'; + +export interface FinanceDetails { + purchase: XpsPurchase; + finance: XpsFinance; + cartItems: CartItem[]; + cartSummary: CartSummary; + paymentCountry: string; +} diff --git a/src/core/finance-details/price.interface.ts b/src/core/finance-details/price.interface.ts new file mode 100644 index 00000000..785afa71 --- /dev/null +++ b/src/core/finance-details/price.interface.ts @@ -0,0 +1,4 @@ +export interface Price { + amount: number | null; + currency: string; +} diff --git a/src/core/finance-details/virtual-currency.interface.ts b/src/core/finance-details/virtual-currency.interface.ts new file mode 100644 index 00000000..8bc74a0f --- /dev/null +++ b/src/core/finance-details/virtual-currency.interface.ts @@ -0,0 +1,10 @@ +export interface VirtualCurrency { + quantity: number; + amount: number | null; + name: string; + image_url: string; + description: string; + longDescription: string | null; + is_bonus: boolean; + currency: string; +} diff --git a/src/core/finance-details/xps-finance.interface.ts b/src/core/finance-details/xps-finance.interface.ts new file mode 100644 index 00000000..0e5a9f73 --- /dev/null +++ b/src/core/finance-details/xps-finance.interface.ts @@ -0,0 +1,32 @@ +export interface XpsFinance { + payment_country: { iso: string }; + sub_total: { + amount: number; + currency: string; + payment_amount: number; + payment_currency: string; + }; + discount: { amount: number; currency: string }; + vat_user?: { + amount: number; + percent: number; + currency: string; + visible: boolean; + }; + sales_tax: { amount: number; percent: number; currency: string }; + sales_tax_user?: { amount: number; percent: number; currency: string }; + fee: { amount: number; currency: string }; + total: { amount: number; currency: string }; + xsolla_credits?: { + payment_amount: number; + payment_currency: string; + }; + vat: { + amount: number; + percent: number; + currency: string; + visible: boolean; + }; + user_balance?: { amount: number; currency: string }; + grand_total?: { amount: number; currency: string }; +} diff --git a/src/core/finance-details/xps-purchase.interface.ts b/src/core/finance-details/xps-purchase.interface.ts new file mode 100644 index 00000000..24f4e6c0 --- /dev/null +++ b/src/core/finance-details/xps-purchase.interface.ts @@ -0,0 +1,14 @@ +import { CheckoutItem } from './checkout-item.interface'; +import { VirtualCurrency } from './virtual-currency.interface'; + +export interface XpsPurchase { + virtual_currency?: VirtualCurrency[]; + checkout_items?: CheckoutItem[]; + virtual_items?: VirtualCurrency[]; + checkout?: { + amount: number; + currency: string; + description: string; + is_bonus?: boolean; + }; +} diff --git a/src/core/form/field-status.interface.ts b/src/core/form/field-status.interface.ts new file mode 100644 index 00000000..5aaa862c --- /dev/null +++ b/src/core/form/field-status.interface.ts @@ -0,0 +1,9 @@ +import { ValidationErrors } from './validation-errors.interface'; + +export interface FieldStatus { + name: string; + validationStatus: 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED'; + errors: ValidationErrors | null; + isFocused?: boolean; + isTouched?: boolean; +} diff --git a/src/core/form/field.interface.ts b/src/core/form/field.interface.ts new file mode 100644 index 00000000..51dae48a --- /dev/null +++ b/src/core/form/field.interface.ts @@ -0,0 +1,24 @@ +import { XpsBoolean } from '../xps-boolean.enum'; + +export interface Field { + name: string; + type: string; + value: string; + title: string; + example: string; + options?: unknown[]; + isMandatory: XpsBoolean; + isReadonly?: XpsBoolean; + isVisible: XpsBoolean; + tooltip?: string; + regex?: string; + validation_error_msg?: string; + label_hint?: string; + javascript: { + change: { + mutableFields: string[]; + staticParams: { [key: string]: string }; + params: string[]; + }; + }; +} diff --git a/src/core/form/fields-type.enum.ts b/src/core/form/fields-type.enum.ts new file mode 100644 index 00000000..6b00994b --- /dev/null +++ b/src/core/form/fields-type.enum.ts @@ -0,0 +1,5 @@ +export enum FieldsType { + text = 'text', + check = 'check', + select = 'select', +} diff --git a/src/core/form/form-configuration.interface.ts b/src/core/form/form-configuration.interface.ts new file mode 100644 index 00000000..a9807751 --- /dev/null +++ b/src/core/form/form-configuration.interface.ts @@ -0,0 +1,8 @@ +export interface FormConfiguration { + paymentMethodId: number; + returnUrl: string; + country?: string; + paymentWithSavedMethod?: boolean; + savedMethodId?: number; + savePaymentMethod?: boolean; +} diff --git a/src/core/form/form-error.interface.ts b/src/core/form/form-error.interface.ts new file mode 100644 index 00000000..079268a0 --- /dev/null +++ b/src/core/form/form-error.interface.ts @@ -0,0 +1,16 @@ +export interface FormError { + code: number; + message: string; + element_name?: string; + alternative_instances?: Array<{ + id: number; + rank: number; + name: string; + aliases: string; + cat: number[]; + is_visible: boolean; + recommended: 0 | 1; + enabled_save_account: null | unknown[]; + switch_icon_name: string | null; + }>; +} diff --git a/src/core/form/form-fields-status.interface.ts b/src/core/form/form-fields-status.interface.ts new file mode 100644 index 00000000..e18dc734 --- /dev/null +++ b/src/core/form/form-fields-status.interface.ts @@ -0,0 +1,5 @@ +import { FieldStatus } from './field-status.interface'; + +export interface FormFieldsStatus { + [key: string]: FieldStatus; +} diff --git a/src/core/form/form-loader.ts b/src/core/form/form-loader.ts new file mode 100644 index 00000000..47af2bb4 --- /dev/null +++ b/src/core/form/form-loader.ts @@ -0,0 +1,70 @@ +import { singleton } from 'tsyringe'; +import { Field } from './field.interface'; +import { FieldsType } from './fields-type.enum'; + +@singleton() +export class FormLoader { + private _fields!: { [key: string]: boolean }; + private _isAllFieldsLoaded!: Promise; + + private _resolve!: () => void; + private _isPromiseResolved = false; + + public async setupAndAwaitFieldsLoading(fields: Field[]): Promise { + this._isAllFieldsLoaded = new Promise((resolve) => { + this._resolve = () => { + resolve(); + }; + }); + + this._isPromiseResolved = false; + this._fields = {}; + + const filteredFields = this.filterFields(fields); + + const isFormWithoutFields = !filteredFields.length; + + if (isFormWithoutFields) { + this._resolve(); + this._isPromiseResolved = true; + } + + filteredFields.forEach((field) => { + this._fields[field.name] = false; + }); + + return this._isAllFieldsLoaded; + } + + public setFieldLoaded(name: string): void { + if (!this._fields) { + return; + } + + if (name in this._fields) { + this._fields[name] = true; + } + + if (this.isAllFieldsLoaded && !this._isPromiseResolved) { + this._resolve(); + this._isPromiseResolved = true; + } + } + + private get isAllFieldsLoaded(): boolean { + return Object.values(this._fields).every((value) => value); + } + + private filterFields(fields: Field[]): Field[] { + return fields.filter((field) => { + const isTextControl = field.type === FieldsType.text; + const isCheckboxControl = field.type === FieldsType.check; + const isSelectControl = field.type === FieldsType.select; + const isQrCodeControl = field.name === 'qr'; + + return ( + isTextControl || isCheckboxControl || isSelectControl || isQrCodeControl + ); + }); + } +} diff --git a/src/core/form/form-message.interface.ts b/src/core/form/form-message.interface.ts new file mode 100644 index 00000000..cd3e209c --- /dev/null +++ b/src/core/form/form-message.interface.ts @@ -0,0 +1,4 @@ +export interface FormMessage { + code: string; + message: string; +} diff --git a/src/core/form/form.interface.ts b/src/core/form/form.interface.ts new file mode 100644 index 00000000..88b7851f --- /dev/null +++ b/src/core/form/form.interface.ts @@ -0,0 +1,6 @@ +import { Field } from './field.interface'; + +export interface Form { + fields: Field[]; + pid: number | null; +} diff --git a/src/core/form/validation-errors.interface.ts b/src/core/form/validation-errors.interface.ts new file mode 100644 index 00000000..0c4d0fe2 --- /dev/null +++ b/src/core/form/validation-errors.interface.ts @@ -0,0 +1,6 @@ +export interface ValidationErrors { + [key: string]: { + [key: string]: unknown; + message?: string; + }; +} diff --git a/src/core/guards/apple-pay/apple-pay-error.guard.spec.ts b/src/core/guards/apple-pay/apple-pay-error.guard.spec.ts new file mode 100644 index 00000000..e41b3da2 --- /dev/null +++ b/src/core/guards/apple-pay/apple-pay-error.guard.spec.ts @@ -0,0 +1,14 @@ +import 'reflect-metadata'; +import { isApplePayErrorEventMessage } from './apple-pay-error.guard'; +import { EventName } from '../../event-name.enum'; + +describe('Event message type guard', () => { + it('Should return true', () => { + expect( + isApplePayErrorEventMessage({ name: EventName.applePayError }) + ).toBeTruthy(); + }); + it('Should return false', () => { + expect(isApplePayErrorEventMessage({})).toBeFalsy(); + }); +}); diff --git a/src/core/guards/apple-pay/apple-pay-error.guard.ts b/src/core/guards/apple-pay/apple-pay-error.guard.ts new file mode 100644 index 00000000..72b059d1 --- /dev/null +++ b/src/core/guards/apple-pay/apple-pay-error.guard.ts @@ -0,0 +1,12 @@ +import { Message } from '../../message.interface'; +import { isEventMessage } from '../event-message.guard'; +import { EventName } from '../../event-name.enum'; + +export const isApplePayErrorEventMessage = ( + messageData: unknown +): messageData is Message<{ error: string } | null | undefined> => { + if (isEventMessage(messageData)) { + return messageData.name === EventName.applePayError; + } + return false; +}; diff --git a/src/core/guards/apple-pay/open-apple-pay-page.guard.spec.ts b/src/core/guards/apple-pay/open-apple-pay-page.guard.spec.ts new file mode 100644 index 00000000..3dfd458c --- /dev/null +++ b/src/core/guards/apple-pay/open-apple-pay-page.guard.spec.ts @@ -0,0 +1,14 @@ +import 'reflect-metadata'; +import { EventName } from '../../event-name.enum'; +import { isOpenApplePayPageEventMessage } from './open-apple-pay-page.guard'; + +describe('Event message type guard', () => { + it('Should return true', () => { + expect( + isOpenApplePayPageEventMessage({ name: EventName.openApplePayPage }) + ).toBeTruthy(); + }); + it('Should return false', () => { + expect(isOpenApplePayPageEventMessage({})).toBeFalsy(); + }); +}); diff --git a/src/core/guards/apple-pay/open-apple-pay-page.guard.ts b/src/core/guards/apple-pay/open-apple-pay-page.guard.ts new file mode 100644 index 00000000..b62dc0c2 --- /dev/null +++ b/src/core/guards/apple-pay/open-apple-pay-page.guard.ts @@ -0,0 +1,12 @@ +import { Message } from '../../message.interface'; +import { isEventMessage } from '../event-message.guard'; +import { EventName } from '../../event-name.enum'; + +export const isOpenApplePayPageEventMessage = ( + messageData: unknown +): messageData is Message<{ redirectUrl: string } | null | undefined> => { + if (isEventMessage(messageData)) { + return messageData.name === EventName.openApplePayPage; + } + return false; +}; diff --git a/src/core/guards/combined-payment-methods-event-message.guard.ts b/src/core/guards/combined-payment-methods-event-message.guard.ts new file mode 100644 index 00000000..aea5aa49 --- /dev/null +++ b/src/core/guards/combined-payment-methods-event-message.guard.ts @@ -0,0 +1,22 @@ +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; +import { CombinedPaymentMethods } from '../combined-payment-methods.interface'; + +export const isCombinedMethodsEventMessage = ( + messageData: unknown, +): messageData is Message<{ + combinedPaymentMethods: CombinedPaymentMethods; +}> => { + if (isEventMessage(messageData)) { + return ( + messageData.name === EventName.getCombinedPaymentMethods && + !!( + messageData.data as { + [key: string]: unknown; + } + )?.combinedPaymentMethods + ); + } + return false; +}; diff --git a/src/core/guards/control-config-event-message.guard.ts b/src/core/guards/control-config-event-message.guard.ts new file mode 100644 index 00000000..bcb015c6 --- /dev/null +++ b/src/core/guards/control-config-event-message.guard.ts @@ -0,0 +1,15 @@ +import { Message } from '../message.interface'; +import { EventName } from '../event-name.enum'; +import { isEventMessage } from './event-message.guard'; + +export const isControlConfigEventMessage = ( + messageData: unknown +): messageData is Message<{ config: T }> => { + if (isEventMessage(messageData)) { + return ( + messageData.name === EventName.getControlComponentConfig && + (messageData.data as { [key: string]: unknown })?.config !== undefined + ); + } + return false; +}; diff --git a/src/core/guards/country-list-event-message.guard.ts b/src/core/guards/country-list-event-message.guard.ts new file mode 100644 index 00000000..da9975bf --- /dev/null +++ b/src/core/guards/country-list-event-message.guard.ts @@ -0,0 +1,20 @@ +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; +import { CountryResponse } from '../country-response.interface'; + +export const isCountryListEventMessage = ( + messageData: unknown, +): messageData is Message<{ + countryList: CountryResponse['countryList']; + currentCountry: string; +}> => { + if (isEventMessage(messageData)) { + return ( + messageData.name === EventName.getCountryList && + (messageData.data as { [key: string]: unknown })?.countryList !== + undefined + ); + } + return false; +}; diff --git a/src/core/guards/delete-saved-method-message.guard.spec.ts b/src/core/guards/delete-saved-method-message.guard.spec.ts new file mode 100644 index 00000000..06af527c --- /dev/null +++ b/src/core/guards/delete-saved-method-message.guard.spec.ts @@ -0,0 +1,13 @@ +import { EventName } from '../event-name.enum'; +import { isDeleteSavedMethodMessage } from './delete-saved-method-message.guard'; + +describe('Error event message type guard', () => { + it('Should return true', () => { + expect( + isDeleteSavedMethodMessage({ name: EventName.deleteSavedMethod }), + ).toBeTruthy(); + }); + it('Should return false', () => { + expect(isDeleteSavedMethodMessage({})).toBeFalsy(); + }); +}); diff --git a/src/core/guards/delete-saved-method-message.guard.ts b/src/core/guards/delete-saved-method-message.guard.ts new file mode 100644 index 00000000..af8c611c --- /dev/null +++ b/src/core/guards/delete-saved-method-message.guard.ts @@ -0,0 +1,9 @@ +import { EventName } from '../event-name.enum'; +import { Message } from '../message.interface'; +import { isEventMessage } from './event-message.guard'; + +export const isDeleteSavedMethodMessage = ( + messageData: unknown, +): messageData is Message<{ isDeleteSuccessful: boolean }> => + isEventMessage(messageData) && + messageData.name === EventName.deleteSavedMethod; diff --git a/src/core/guards/error-event-message.guard.spec.ts b/src/core/guards/error-event-message.guard.spec.ts new file mode 100644 index 00000000..023281be --- /dev/null +++ b/src/core/guards/error-event-message.guard.spec.ts @@ -0,0 +1,11 @@ +import { EventName } from '../event-name.enum'; +import { isErrorEventMessage } from './error-event-message.guard'; + +describe('Error event message type guard', () => { + it('Should return true', () => { + expect(isErrorEventMessage({ name: EventName.error })).toBeTruthy(); + }); + it('Should return false', () => { + expect(isErrorEventMessage({})).toBeFalsy(); + }); +}); diff --git a/src/core/guards/error-event-message.guard.ts b/src/core/guards/error-event-message.guard.ts new file mode 100644 index 00000000..62462a0c --- /dev/null +++ b/src/core/guards/error-event-message.guard.ts @@ -0,0 +1,8 @@ +import { EventName } from '../event-name.enum'; +import { Message } from '../message.interface'; +import { isEventMessage } from './event-message.guard'; + +export const isErrorEventMessage = ( + messageData: unknown +): messageData is Message<{ error: string }> => + isEventMessage(messageData) && messageData.name === EventName.error; diff --git a/src/core/guards/event-message.guard.spec.ts b/src/core/guards/event-message.guard.spec.ts new file mode 100644 index 00000000..e9707a8d --- /dev/null +++ b/src/core/guards/event-message.guard.spec.ts @@ -0,0 +1,12 @@ +import 'reflect-metadata'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; + +describe('Event message type guard', () => { + it('Should return true', () => { + expect(isEventMessage({ name: EventName.initPayment })).toBeTruthy(); + }); + it('Should return false', () => { + expect(isEventMessage({})).toBeFalsy(); + }); +}); diff --git a/src/core/guards/event-message.guard.ts b/src/core/guards/event-message.guard.ts new file mode 100644 index 00000000..593577ac --- /dev/null +++ b/src/core/guards/event-message.guard.ts @@ -0,0 +1,4 @@ +import { Message } from '../message.interface'; + +export const isEventMessage = (messageData: unknown): messageData is Message => + !!(messageData as Message).name; diff --git a/src/core/guards/finance-details-message.guard.ts b/src/core/guards/finance-details-message.guard.ts new file mode 100644 index 00000000..c35a4a0b --- /dev/null +++ b/src/core/guards/finance-details-message.guard.ts @@ -0,0 +1,14 @@ +import { EventName } from '../../core/event-name.enum'; +import { Message } from '../../core/message.interface'; +import { FinanceDetails } from '../finance-details/finance-details.interface'; +import { isEventMessage } from './event-message.guard'; + +export const isFinanceDetailsEventMessage = ( + messageData: unknown +): messageData is Message => { + if (isEventMessage(messageData)) { + return messageData.name === EventName.financeDetails; + } + + return false; +}; diff --git a/src/core/guards/finish-load-component.guard.ts b/src/core/guards/finish-load-component.guard.ts new file mode 100644 index 00000000..8d364bd0 --- /dev/null +++ b/src/core/guards/finish-load-component.guard.ts @@ -0,0 +1,12 @@ +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; + +export const isFinishLoadComponentEventMessage = ( + messageData: unknown +): messageData is Message<{ fieldName: string } | null | undefined> => { + if (isEventMessage(messageData)) { + return messageData.name === EventName.finishLoadComponent; + } + return false; +}; diff --git a/src/core/guards/form-fields-status-changed-message.guard.spec.ts b/src/core/guards/form-fields-status-changed-message.guard.spec.ts new file mode 100644 index 00000000..b4c62cc4 --- /dev/null +++ b/src/core/guards/form-fields-status-changed-message.guard.spec.ts @@ -0,0 +1,25 @@ +import { EventName } from '../event-name.enum'; +import { isFormFieldsStatusChangedMessage } from './form-fields-status-changed-message.guard'; + +describe('FormFieldsStatusChanged event message guard', () => { + it('Should return true for matched event name', () => { + const isCorrectEvent = isFormFieldsStatusChangedMessage({ + name: EventName.formFieldsStatusChanged, + }); + + expect(isCorrectEvent).toBeTruthy(); + }); + + it('Should return false for different event name', () => { + const isWrongEvent = isFormFieldsStatusChangedMessage({ + name: EventName.initPayment, + }); + + expect(isWrongEvent).toBeFalsy(); + }); + + it('Should return false for message without name', () => { + const isWrongEvent = isFormFieldsStatusChangedMessage({}); + expect(isWrongEvent).toBeFalsy(); + }); +}); diff --git a/src/core/guards/form-fields-status-changed-message.guard.ts b/src/core/guards/form-fields-status-changed-message.guard.ts new file mode 100644 index 00000000..6234056b --- /dev/null +++ b/src/core/guards/form-fields-status-changed-message.guard.ts @@ -0,0 +1,14 @@ +import { EventName } from '../event-name.enum'; +import { FormFieldsStatus } from '../form/form-fields-status.interface'; +import { Message } from '../message.interface'; +import { isEventMessage } from './event-message.guard'; + +export const isFormFieldsStatusChangedMessage = ( + messageData: unknown, +): messageData is Message => { + if (isEventMessage(messageData)) { + return messageData.name === EventName.formFieldsStatusChanged; + } + + return false; +}; diff --git a/src/core/guards/form-messages.guard.spec.ts b/src/core/guards/form-messages.guard.spec.ts new file mode 100644 index 00000000..7fe6a83c --- /dev/null +++ b/src/core/guards/form-messages.guard.spec.ts @@ -0,0 +1,25 @@ +import { EventName } from '../event-name.enum'; +import { isFormMessagesEventMessage } from './form-messages.guard'; + +describe('isFormMessagesEventMessage event message guard', () => { + it('Should return true for matched event name', () => { + const isCorrectEvent = isFormMessagesEventMessage({ + name: EventName.formMessagesChanged, + }); + + expect(isCorrectEvent).toBeTruthy(); + }); + + it('Should return false for different event name', () => { + const isWrongEvent = isFormMessagesEventMessage({ + name: EventName.initPayment, + }); + + expect(isWrongEvent).toBeFalsy(); + }); + + it('Should return false for message without name', () => { + const isWrongEvent = isFormMessagesEventMessage({}); + expect(isWrongEvent).toBeFalsy(); + }); +}); diff --git a/src/core/guards/form-messages.guard.ts b/src/core/guards/form-messages.guard.ts new file mode 100644 index 00000000..f3951660 --- /dev/null +++ b/src/core/guards/form-messages.guard.ts @@ -0,0 +1,13 @@ +import { EventName } from '../../core/event-name.enum'; +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; + +export const isFormMessagesEventMessage = ( + messageData: unknown +): messageData is Message => { + if (isEventMessage(messageData)) { + return messageData.name === EventName.formMessagesChanged; + } + + return false; +}; diff --git a/src/core/guards/get-payment-status-message.guard.spec.ts b/src/core/guards/get-payment-status-message.guard.spec.ts new file mode 100644 index 00000000..4928f06a --- /dev/null +++ b/src/core/guards/get-payment-status-message.guard.spec.ts @@ -0,0 +1,19 @@ +import { EventName } from '../event-name.enum'; +import { isGetPaymentStatusMessage } from './get-payment-status-message.guard'; + +describe('GetPaymentStatus event message guard', () => { + it('Should return true', () => { + const isCorrectEvent = isGetPaymentStatusMessage({ + name: EventName.getPaymentStatus, + }); + + expect(isCorrectEvent).toBeTruthy(); + }); + it('Should return false', () => { + const isWrongEvent = isGetPaymentStatusMessage({ + name: EventName.initPayment, + }); + + expect(isWrongEvent).toBeFalsy(); + }); +}); diff --git a/src/core/guards/get-payment-status-message.guard.ts b/src/core/guards/get-payment-status-message.guard.ts new file mode 100644 index 00000000..727459bb --- /dev/null +++ b/src/core/guards/get-payment-status-message.guard.ts @@ -0,0 +1,14 @@ +import { Message } from '../message.interface'; +import { Status } from '../status/status.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../event-name.enum'; + +export const isGetPaymentStatusMessage = ( + messageData: unknown +): messageData is Message => { + if (isEventMessage(messageData)) { + return messageData.name === EventName.getPaymentStatus; + } + + return false; +}; diff --git a/src/core/guards/init-form-event-message.guard.ts b/src/core/guards/init-form-event-message.guard.ts new file mode 100644 index 00000000..6ab8ab70 --- /dev/null +++ b/src/core/guards/init-form-event-message.guard.ts @@ -0,0 +1,14 @@ +import { EventName } from '../../core/event-name.enum'; +import { Message } from '../../core/message.interface'; +import { Form } from '../form/form.interface'; +import { isEventMessage } from './event-message.guard'; + +export const isInitFormEventMessage = ( + messageData: unknown +): messageData is Message
=> { + if (isEventMessage(messageData)) { + return messageData.name === EventName.initForm; + } + + return false; +}; diff --git a/src/core/guards/legal-config-event-message.guard.ts b/src/core/guards/legal-config-event-message.guard.ts new file mode 100644 index 00000000..0cc81f99 --- /dev/null +++ b/src/core/guards/legal-config-event-message.guard.ts @@ -0,0 +1,16 @@ +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; +import { LegalComponentConfig } from '../../features/headless-checkout/web-components/legal/legal-component.config.interface'; + +export const isLegalConfigEventMessage = ( + messageData: unknown +): messageData is Message<{ config: LegalComponentConfig }> => { + if (isEventMessage(messageData)) { + return ( + messageData.name === EventName.getLegalComponentConfig && + (messageData.data as { [key: string]: unknown })?.config !== undefined + ); + } + return false; +}; diff --git a/src/core/guards/methods-event-message.guard.spec.ts b/src/core/guards/methods-event-message.guard.spec.ts new file mode 100644 index 00000000..fa25c4c7 --- /dev/null +++ b/src/core/guards/methods-event-message.guard.spec.ts @@ -0,0 +1,20 @@ +import { EventName } from '../../core/event-name.enum'; +import { isMethodsEventMessage } from './methods-event-message.guard'; + +describe('Methods event message guard', () => { + it('Should return true', () => { + const isPaymentMethodsMessage = isMethodsEventMessage({ + name: EventName.getPaymentMethodsList, + data: { methods: [] }, + }); + + expect(isPaymentMethodsMessage).toBeTruthy(); + }); + it('Should return false', () => { + const isPaymentMethodsMessage = isMethodsEventMessage({ + name: EventName.initPayment, + }); + + expect(isPaymentMethodsMessage).toBeFalsy(); + }); +}); diff --git a/src/core/guards/methods-event-message.guard.ts b/src/core/guards/methods-event-message.guard.ts new file mode 100644 index 00000000..6527fe1d --- /dev/null +++ b/src/core/guards/methods-event-message.guard.ts @@ -0,0 +1,17 @@ +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; +import { PaymentMethod } from '../../core/payment-method.interface'; + +export const isMethodsEventMessage = ( + messageData: unknown +): messageData is Message<{ methods: PaymentMethod[] }> => { + if (isEventMessage(messageData)) { + return ( + messageData.name === EventName.getPaymentMethodsList || + (messageData.name === EventName.getPaymentQuickMethods && + (messageData.data as { [key: string]: unknown })?.methods !== undefined) + ); + } + return false; +}; diff --git a/src/core/guards/next-action-event-message.guard.ts b/src/core/guards/next-action-event-message.guard.ts new file mode 100644 index 00000000..133f1e32 --- /dev/null +++ b/src/core/guards/next-action-event-message.guard.ts @@ -0,0 +1,14 @@ +import { NextAction } from '../actions/next-action.interface'; +import { EventName } from '../event-name.enum'; +import { Message } from '../message.interface'; +import { isEventMessage } from './event-message.guard'; + +export const isNextActionEventMessage = ( + messageData: unknown +): messageData is Message => { + if (isEventMessage(messageData)) { + return messageData.name === EventName.nextAction; + } + + return false; +}; diff --git a/src/core/guards/saved-methods-event-message.guard.spec.ts b/src/core/guards/saved-methods-event-message.guard.spec.ts new file mode 100644 index 00000000..41c821c1 --- /dev/null +++ b/src/core/guards/saved-methods-event-message.guard.spec.ts @@ -0,0 +1,20 @@ +import { EventName } from '../../core/event-name.enum'; +import { isSavedMethodsEventMessage } from './saved-methods-event-message.guard'; + +describe('Saved methods event message guard', () => { + it('Should return true', () => { + const isSavedMethodMessage = isSavedMethodsEventMessage({ + name: EventName.getSavedMethods, + data: { methods: [] }, + }); + + expect(isSavedMethodMessage).toBeTruthy(); + }); + it('Should return false', () => { + const isSavedMethodMessage = isSavedMethodsEventMessage({ + name: EventName.initPayment, + }); + + expect(isSavedMethodMessage).toBeFalsy(); + }); +}); diff --git a/src/core/guards/saved-methods-event-message.guard.ts b/src/core/guards/saved-methods-event-message.guard.ts new file mode 100644 index 00000000..dcf1d4cd --- /dev/null +++ b/src/core/guards/saved-methods-event-message.guard.ts @@ -0,0 +1,16 @@ +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; +import { SavedMethod } from '../saved-method.interface'; + +export const isSavedMethodsEventMessage = ( + messageData: unknown +): messageData is Message<{ methods: SavedMethod[] }> => { + if (isEventMessage(messageData)) { + return ( + messageData.name === EventName.getSavedMethods && + (messageData.data as { [key: string]: unknown })?.methods !== undefined + ); + } + return false; +}; diff --git a/src/core/guards/submit-button-loading-message.guard.ts b/src/core/guards/submit-button-loading-message.guard.ts new file mode 100644 index 00000000..078736ed --- /dev/null +++ b/src/core/guards/submit-button-loading-message.guard.ts @@ -0,0 +1,8 @@ +import { Message } from '../message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../event-name.enum'; + +export const isSubmitButtonLoadingMessage = ( + messageData: unknown, +): messageData is Message<{ formStatus: string }> => + isEventMessage(messageData) && messageData.name === EventName.getFormStatus; diff --git a/src/core/guards/text-config-event-message.guard.ts b/src/core/guards/text-config-event-message.guard.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/core/guards/update-credit-card-type.guard.ts b/src/core/guards/update-credit-card-type.guard.ts new file mode 100644 index 00000000..160ca2b5 --- /dev/null +++ b/src/core/guards/update-credit-card-type.guard.ts @@ -0,0 +1,15 @@ +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; + +export const isUpdateCreditCardTypeMessage = ( + messageData: unknown +): messageData is Message<{ cardType: string }> => { + if (isEventMessage(messageData)) { + return ( + messageData.name === EventName.updateCreditCardType && + (messageData.data as { [key: string]: unknown })?.cardType !== undefined + ); + } + return false; +}; diff --git a/src/core/guards/user-balance-event-message.guard.ts b/src/core/guards/user-balance-event-message.guard.ts new file mode 100644 index 00000000..e9889e9c --- /dev/null +++ b/src/core/guards/user-balance-event-message.guard.ts @@ -0,0 +1,17 @@ +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; +import { UserBalance } from '../user-balance.interface'; + +export const isUserBalanceEventMessage = ( + messageData: unknown +): messageData is Message<{ userBalance: UserBalance }> => { + if (isEventMessage(messageData)) { + return ( + messageData.name === EventName.getUserBalance && + (messageData.data as { [key: string]: unknown })?.userBalance !== + undefined + ); + } + return false; +}; diff --git a/src/core/guards/user-balance-message.guard.spec.ts b/src/core/guards/user-balance-message.guard.spec.ts new file mode 100644 index 00000000..87940d49 --- /dev/null +++ b/src/core/guards/user-balance-message.guard.spec.ts @@ -0,0 +1,26 @@ +import { EventName } from '../../core/event-name.enum'; +import { isUserBalanceEventMessage } from './user-balance-event-message.guard'; + +describe('User balance event message guard', () => { + it('Should return true', () => { + const isUserBalanceMessage = isUserBalanceEventMessage({ + name: EventName.getUserBalance, + data: { + userBalance: { + isEnoughUserBalance: false, + isEnoughUserRecurrentBalance: false, + }, + }, + }); + + expect(isUserBalanceMessage).toBeTruthy(); + }); + + it('Should return false', () => { + const isUserBalanceMessage = isUserBalanceEventMessage({ + name: EventName.initPayment, + }); + + expect(isUserBalanceMessage).toBeFalsy(); + }); +}); diff --git a/src/core/guards/user-balance-value-event-message.guard.spec.ts b/src/core/guards/user-balance-value-event-message.guard.spec.ts new file mode 100644 index 00000000..dd0b4de4 --- /dev/null +++ b/src/core/guards/user-balance-value-event-message.guard.spec.ts @@ -0,0 +1,21 @@ +import { EventName } from '../../core/event-name.enum'; +import { isUserBalanceValueEventMessage } from './user-balance-value-event-message.guard'; + +describe('User balance value event message guard', () => { + it('Should return true', () => { + const isUserBalanceMessage = isUserBalanceValueEventMessage({ + name: EventName.getUserBalanceValue, + data: { amount: 10, currency: 'USD' }, + }); + + expect(isUserBalanceMessage).toBeTruthy(); + }); + + it('Should return false', () => { + const isUserBalanceMessage = isUserBalanceValueEventMessage({ + name: EventName.initPayment, + }); + + expect(isUserBalanceMessage).toBeFalsy(); + }); +}); diff --git a/src/core/guards/user-balance-value-event-message.guard.ts b/src/core/guards/user-balance-value-event-message.guard.ts new file mode 100644 index 00000000..593670fb --- /dev/null +++ b/src/core/guards/user-balance-value-event-message.guard.ts @@ -0,0 +1,13 @@ +import { Message } from '../../core/message.interface'; +import { isEventMessage } from './event-message.guard'; +import { EventName } from '../../core/event-name.enum'; +import { Balance } from '../balance.interface'; + +export const isUserBalanceValueEventMessage = ( + messageData: unknown +): messageData is Message => { + if (isEventMessage(messageData)) { + return messageData.name === EventName.getUserBalanceValue; + } + return false; +}; diff --git a/src/core/i18n/dictionary-loader.function.spec.ts b/src/core/i18n/dictionary-loader.function.spec.ts new file mode 100644 index 00000000..dc2cbf52 --- /dev/null +++ b/src/core/i18n/dictionary-loader.function.spec.ts @@ -0,0 +1,11 @@ +import { loadDictionaries } from './dictionary-loader.function'; + +describe('DictionaryLoader', () => { + it('Should load dictionary with 20 languages', () => { + const supportLangCount = 20; + + const result = loadDictionaries(); + + expect(Object.keys(result).length).toBe(supportLangCount); + }); +}); diff --git a/src/core/i18n/dictionary-loader.function.ts b/src/core/i18n/dictionary-loader.function.ts new file mode 100644 index 00000000..0c6113e3 --- /dev/null +++ b/src/core/i18n/dictionary-loader.function.ts @@ -0,0 +1,46 @@ +import { Dictionary } from './dictionary.interface'; +import ar from '../../translations/ar.json'; +import bg from '../../translations/bg.json'; +import cs from '../../translations/cs.json'; +import de from '../../translations/de.json'; +import en from '../../translations/en.json'; +import es from '../../translations/es.json'; +import fr from '../../translations/fr.json'; +import he from '../../translations/he.json'; +import it from '../../translations/it.json'; +import ja from '../../translations/ja.json'; +import ko from '../../translations/ko.json'; +import pl from '../../translations/pl.json'; +import pt from '../../translations/pt.json'; +import ro from '../../translations/ro.json'; +import ru from '../../translations/ru.json'; +import th from '../../translations/th.json'; +import tr from '../../translations/tr.json'; +import vi from '../../translations/vi.json'; +import zhHans from '../../translations/zh_HANS.json'; +import zhHant from '../../translations/zh_HANT.json'; + +export const loadDictionaries = (): Dictionary => { + return { + ...ar, + ...bg, + ...cs, + ...de, + ...en, + ...es, + ...fr, + ...he, + ...it, + ...ja, + ...ko, + ...pl, + ...pt, + ...ro, + ...ru, + ...th, + ...tr, + ...vi, + ...zhHans, + ...zhHant, + }; +}; diff --git a/src/core/i18n/dictionary.interface.ts b/src/core/i18n/dictionary.interface.ts new file mode 100644 index 00000000..5dad3e4f --- /dev/null +++ b/src/core/i18n/dictionary.interface.ts @@ -0,0 +1,9 @@ +import { Lang } from './lang.enum'; + +export type Dictionary = { + [key in Lang]?: { + translation: { + [key: string]: string; + }; + }; +}; diff --git a/src/core/i18n/lang.enum.ts b/src/core/i18n/lang.enum.ts new file mode 100644 index 00000000..f516f737 --- /dev/null +++ b/src/core/i18n/lang.enum.ts @@ -0,0 +1,22 @@ +export enum Lang { + AR = 'ar', + BG = 'bg', + CS = 'cs', + CN = 'cn', + DE = 'de', + EN = 'en', + ES = 'es', + FR = 'fr', + HE = 'he', + IT = 'it', + JA = 'ja', + KO = 'ko', + PL = 'pl', + PT = 'pt', + RO = 'ro', + RU = 'ru', + TH = 'th', + TR = 'tr', + TW = 'tw', + VI = 'vi', +} diff --git a/src/core/i18n/legacy-lang.enum.ts b/src/core/i18n/legacy-lang.enum.ts new file mode 100644 index 00000000..d2373816 --- /dev/null +++ b/src/core/i18n/legacy-lang.enum.ts @@ -0,0 +1,4 @@ +export enum LegacyLang { + ZH_HANS = 'zh_HANS', + ZH_HANT = 'zh_HANT', +} diff --git a/src/core/i18n/localize.service.spec.ts b/src/core/i18n/localize.service.spec.ts new file mode 100644 index 00000000..ee311b64 --- /dev/null +++ b/src/core/i18n/localize.service.spec.ts @@ -0,0 +1,31 @@ +import i18next from 'i18next'; +import { container } from 'tsyringe'; +import { LocalizeService } from './localize.service'; + +describe('LocalizeService', () => { + let localizeService: LocalizeService; + + beforeEach(() => { + container.clearInstances(); + + localizeService = container.createChildContainer().resolve(LocalizeService); + }); + + it('Should init dictionaries', () => { + const spy = spyOn(i18next, 'init'); + + void localizeService.initDictionaries(); + + expect(spy).toHaveBeenCalled(); + }); + + it('Should translate', () => { + const expectedValue = 'Hello World!'; + + spyOn(i18next, 't').and.returnValue(expectedValue); + + const result = localizeService.translate('hello'); + + expect(result).toEqual(expectedValue); + }); +}); diff --git a/src/core/i18n/localize.service.ts b/src/core/i18n/localize.service.ts new file mode 100644 index 00000000..7a949d0a --- /dev/null +++ b/src/core/i18n/localize.service.ts @@ -0,0 +1,39 @@ +import * as i18next from 'i18next'; +import { singleton } from 'tsyringe'; +import { loadDictionaries } from './dictionary-loader.function'; +import { Lang } from './lang.enum'; +import { LegacyLang } from './legacy-lang.enum'; + +@singleton() +export class LocalizeService { + public async initDictionaries(locale: Lang = Lang.EN): Promise { + // fallback for legacy locale + if (locale === Lang.CN) { + locale = LegacyLang.ZH_HANS as unknown as Lang; + } + + if (locale === Lang.TW) { + locale = LegacyLang.ZH_HANT as unknown as Lang; + } + + await i18next.init({ + lng: locale, + fallbackLng: Lang.EN, + supportedLngs: [ + ...Object.values(Lang), + LegacyLang.ZH_HANT, + LegacyLang.ZH_HANS, + ], + debug: false, + resources: loadDictionaries(), + }); + } + + public translate(key: string): string { + return i18next.t(key); + } + + public getAvailableLanguages(): Lang[] { + return Object.values({ ...Lang }); + } +} diff --git a/src/core/message.interface.ts b/src/core/message.interface.ts new file mode 100644 index 00000000..fe2355ce --- /dev/null +++ b/src/core/message.interface.ts @@ -0,0 +1,6 @@ +import { EventName } from './event-name.enum'; + +export interface Message { + name: EventName; + data?: T; +} diff --git a/src/core/payment-method.interface.ts b/src/core/payment-method.interface.ts new file mode 100644 index 00000000..b0c52add --- /dev/null +++ b/src/core/payment-method.interface.ts @@ -0,0 +1,11 @@ +export interface PaymentMethod { + id: number; + rank: number; + name: string; + aliases: string; + categories: number[]; + isVisible: boolean; + recommended: boolean; + enabledSaveAccount: null | unknown[]; + iconName: string | null; +} diff --git a/src/core/pipes/currency/currency-format.interface.ts b/src/core/pipes/currency/currency-format.interface.ts new file mode 100644 index 00000000..88edea0e --- /dev/null +++ b/src/core/pipes/currency/currency-format.interface.ts @@ -0,0 +1,12 @@ +interface SymbolFormat { + grapheme: string; + template: string; + rtl: boolean; +} + +export interface CurrencyFormat { + name: string; + fractionSize: number; + symbol: SymbolFormat; + uniqSymbol: SymbolFormat | null; +} diff --git a/src/core/pipes/currency/currency.pipe.spec.ts b/src/core/pipes/currency/currency.pipe.spec.ts new file mode 100644 index 00000000..8ab5a9cd --- /dev/null +++ b/src/core/pipes/currency/currency.pipe.spec.ts @@ -0,0 +1,55 @@ +import { container } from 'tsyringe'; +import { DecimalPipe } from '../decimal/decimal.pipe'; +import { CurrencyPipe } from './currency.pipe'; + +describe('CurrencyPipe', () => { + let pipe: CurrencyPipe; + let decimalPipe: DecimalPipe; + + beforeEach(() => { + container.clearInstances(); + decimalPipe = { + transform: (value) => value.toString(), + }; + pipe = container + .createChildContainer() + .register(DecimalPipe, { + useValue: decimalPipe, + }) + .resolve(CurrencyPipe); + }); + + it('Should return null for no value', () => { + const currency = pipe.transform(null, 'USD'); + expect(currency).toBeNull(); + }); + + it('Should return null for no currency', () => { + const currency = pipe.transform(1, ''); + expect(currency).toBeNull(); + }); + + it('Should format known currency with configuration', () => { + const amount = 1234567890; + const currency = pipe.transform(amount, 'USD'); + expect(currency).toEqual('US$1234567890'); + }); + + it('Should return null for known currency and invalid amount', () => { + spyOn(decimalPipe, 'transform').and.returnValue(''); + const currency = pipe.transform(1, 'USD'); + expect(currency).toBeNull(); + }); + + it('Should format unknown currency with default template', () => { + const amount = 1234567890; + const currency = pipe.transform(amount, 'AAA'); + expect(currency).toEqual('1234567890 AAA'); + }); + + it('Should return null for unknown currency and invalid amount', () => { + spyOn(decimalPipe, 'transform').and.returnValue(''); + const currency = pipe.transform(1, 'AAA'); + expect(currency).toBeNull(); + }); +}); diff --git a/src/core/pipes/currency/currency.pipe.ts b/src/core/pipes/currency/currency.pipe.ts new file mode 100644 index 00000000..a30a1385 --- /dev/null +++ b/src/core/pipes/currency/currency.pipe.ts @@ -0,0 +1,75 @@ +import currencyFormat from 'currency-format/currency-format.json'; +import { singleton } from 'tsyringe'; +import { DecimalPipe } from '../decimal/decimal.pipe'; +import { PipeTransform } from '../pipe-transform.interface'; +import { CurrencyFormat } from './currency-format.interface'; + +@singleton() +export class CurrencyPipe implements PipeTransform { + public constructor(private readonly decimalPipe: DecimalPipe) {} + + public transform( + value: number | string | null, + currencyCode?: string, + ): string | null { + if (!value?.toString() || !currencyCode) { + return null; + } + + return this.formatCurrency(value, currencyCode); + } + + private getCurrencyConfig(currencyCode: string): CurrencyFormat | null { + // @ts-expect-error json don't have string type index + const formatForCurrency = currencyFormat[ + currencyCode.toUpperCase() + ] as unknown as CurrencyFormat | undefined; + + if (!formatForCurrency) { + return null; + } + + return formatForCurrency; + } + + private formatCurrency( + value: number | string, + currencyCode: string, + ): string | null { + const currencyConfig = this.getCurrencyConfig(currencyCode); + + if (!currencyConfig?.uniqSymbol) { + return this.formatCurrencyWithNoConfig(value, currencyCode); + } + + const amount = this.decimalPipe.transform( + value, + 1, + currencyConfig.fractionSize, + currencyConfig.fractionSize, + ); + + if (!amount) { + return null; + } + + const formattedCurrency = currencyConfig.uniqSymbol.template + .replace('1', amount) + .replace('$', currencyConfig.uniqSymbol.grapheme); + + return formattedCurrency; + } + + private formatCurrencyWithNoConfig( + value: number | string, + currencyCode: string, + ): string | null { + const amount = this.decimalPipe.transform(value, 1, 2, 2); + + if (!amount) { + return null; + } + + return `${amount} ${currencyCode}`; + } +} diff --git a/src/core/pipes/date/date.pipe.spec.ts b/src/core/pipes/date/date.pipe.spec.ts new file mode 100644 index 00000000..e1d3f00b --- /dev/null +++ b/src/core/pipes/date/date.pipe.spec.ts @@ -0,0 +1,28 @@ +import { container } from 'tsyringe'; +import { Lang } from '../../../core/i18n/lang.enum'; +import { DatePipe } from './date.pipe'; + +describe('DatePipe', () => { + let pipe: DatePipe; + + beforeEach(() => { + container.clearInstances(); + + pipe = container.createChildContainer().resolve(DatePipe); + }); + + it('Should return empty string for invalid date', () => { + const value = pipe.transform('invalid-date'); + expect(value).toEqual(''); + }); + + it('Should return formatted date for en language', () => { + const value = pipe.transform('2023-10-10T10:00:00+03:00', Lang.EN); + expect(value).toEqual('10/10/2023'); + }); + + it('Should return formatted date for ru language', () => { + const value = pipe.transform('2023-10-10T10:00:00+03:00', Lang.RU); + expect(value).toEqual('10.10.2023'); + }); +}); diff --git a/src/core/pipes/date/date.pipe.ts b/src/core/pipes/date/date.pipe.ts new file mode 100644 index 00000000..1d306ddc --- /dev/null +++ b/src/core/pipes/date/date.pipe.ts @@ -0,0 +1,19 @@ +import i18next from 'i18next'; +import { singleton } from 'tsyringe'; +import { Lang } from '../../../core/i18n/lang.enum'; +import { PipeTransform } from '../pipe-transform.interface'; +import { localeValidator } from '../../../core/validators/locale/locale.validator'; + +@singleton() +export class DatePipe implements PipeTransform { + public transform(value: string, locale: Lang | null = null): string { + const date = new Date(value); + + if (!date || date.toString() === 'Invalid Date') { + return ''; + } + + const formatLocale = localeValidator(locale ?? (i18next.language as Lang)); + return date.toLocaleDateString(formatLocale); + } +} diff --git a/src/core/pipes/decimal/decimal.pipe.spec.ts b/src/core/pipes/decimal/decimal.pipe.spec.ts new file mode 100644 index 00000000..4cea0239 --- /dev/null +++ b/src/core/pipes/decimal/decimal.pipe.spec.ts @@ -0,0 +1,37 @@ +import { Lang } from '../../../core/i18n/lang.enum'; +import { container } from 'tsyringe'; +import { DecimalPipe } from './decimal.pipe'; + +describe('DecimalPipe', () => { + let pipe: DecimalPipe; + + beforeEach(() => { + container.clearInstances(); + + pipe = container.createChildContainer().resolve(DecimalPipe); + }); + + it('Should return empty string for invalid number', () => { + const value = pipe.transform('no-number'); + expect(value).toEqual(''); + }); + + it('Should format string number with default settings', () => { + const value = pipe.transform('1234567890.123'); + expect(value).toEqual('1,234,567,890.12'); + }); + + it('Should format string number with custom settings', () => { + const minIntDigits = 2; + const minFracDigits = 1; + const maxFracDigits = 3; + const value = pipe.transform( + '1.234', + minIntDigits, + minFracDigits, + maxFracDigits, + Lang.RU, + ); + expect(value).toEqual('01,234'); + }); +}); diff --git a/src/core/pipes/decimal/decimal.pipe.ts b/src/core/pipes/decimal/decimal.pipe.ts new file mode 100644 index 00000000..39ca613f --- /dev/null +++ b/src/core/pipes/decimal/decimal.pipe.ts @@ -0,0 +1,29 @@ +import i18next from 'i18next'; +import { singleton } from 'tsyringe'; +import { Lang } from '../../../core/i18n/lang.enum'; +import { PipeTransform } from '../pipe-transform.interface'; +import { localeValidator } from '../../../core/validators/locale/locale.validator'; + +@singleton() +export class DecimalPipe implements PipeTransform { + public transform( + value: number | string, + minIntegerDigits = 1, + minFracDigits = 2, + maxFracDigits = 2, + locale: Lang | null = null, + ): string { + value = Number(value); + + if (isNaN(value)) { + return ''; + } + + const formatLocale = localeValidator(locale ?? (i18next.language as Lang)); + return new Intl.NumberFormat(formatLocale, { + minimumIntegerDigits: minIntegerDigits, + minimumFractionDigits: minFracDigits, + maximumFractionDigits: maxFracDigits, + }).format(value); + } +} diff --git a/src/core/pipes/pipe-transform.interface.ts b/src/core/pipes/pipe-transform.interface.ts new file mode 100644 index 00000000..ea73a2ee --- /dev/null +++ b/src/core/pipes/pipe-transform.interface.ts @@ -0,0 +1,3 @@ +export interface PipeTransform { + transform(value: unknown, ...args: unknown[]): unknown; +} diff --git a/src/core/post-messages-client/handler.type.ts b/src/core/post-messages-client/handler.type.ts new file mode 100644 index 00000000..fec2edd8 --- /dev/null +++ b/src/core/post-messages-client/handler.type.ts @@ -0,0 +1,6 @@ +import { Message } from '../message.interface'; + +export type Handler = ( + data: Message, + callback?: (args?: unknown) => void | boolean +) => { isHandled: boolean; value?: T } | null; diff --git a/src/core/post-messages-client/post-messages-client.spec.ts b/src/core/post-messages-client/post-messages-client.spec.ts new file mode 100644 index 00000000..236d77cf --- /dev/null +++ b/src/core/post-messages-client/post-messages-client.spec.ts @@ -0,0 +1,107 @@ +import { PostMessagesClient } from './post-messages-client'; +import { Message } from '../message.interface'; +import { EventName } from '../event-name.enum'; +import { Handler } from './handler.type'; +import { container } from 'tsyringe'; +import { PaymentMethod } from '../payment-method.interface'; +import { isMethodsEventMessage } from '../../core/guards/methods-event-message.guard'; + +const mockHandler: Handler = ( + message: Message +): { isHandled: boolean; value: PaymentMethod[] } | null => { + if ( + isMethodsEventMessage(message) && + message.name === EventName.getPaymentMethodsList + ) { + const methods = message.data?.methods; + return { + isHandled: true, + value: methods ?? [], + }; + } + return null; +}; + +const mockPaymentMethod: PaymentMethod = { + id: 1, + rank: 1, + name: 'method', +} as unknown as PaymentMethod; + +const mockMessage: Message = { + name: EventName.getPaymentMethodsList, + data: { + methods: [mockPaymentMethod], + }, +}; +const recipientUrl = 'https://example.ru'; +const mockMessageEvent = { + origin: recipientUrl, + data: JSON.stringify(mockMessage), +} as MessageEvent; + +describe('PostMessagesClient', () => { + let postMessagesClient: PostMessagesClient; + let recipient: HTMLIFrameElement; + + beforeEach(() => { + recipient = window.document.createElement('iframe'); + container.clearInstances(); + postMessagesClient = container + .createChildContainer() + .register(Window, { useValue: window }) + .resolve(PostMessagesClient); + }); + + it('Should throw exception if no recipient', () => { + const promise = postMessagesClient.send(mockMessage, mockHandler); + return expectAsync(promise).toBeRejectedWithError( + 'No recipient for post messages.' + ); + }); + + it('Should return payment methods', async () => { + spyOn(window, 'addEventListener').and.callFake( + (name: string, handlerWrapper: EventListenerOrEventListenerObject) => { + (handlerWrapper as (message: MessageEvent) => void)(mockMessageEvent); + } + ); + + postMessagesClient.init(recipient, recipientUrl); + const value = await postMessagesClient.send(mockMessage, mockHandler); + expect(value).toEqual([mockPaymentMethod]); + }); + + it('Should call callback on event', () => { + spyOn(window, 'addEventListener').and.callFake( + (name: string, handlerWrapper: EventListenerOrEventListenerObject) => { + (handlerWrapper as (message: MessageEvent) => void)(mockMessageEvent); + } + ); + + const callbackSpy = jasmine.createSpy(); + postMessagesClient.init(recipient, recipientUrl); + postMessagesClient.listen( + EventName.getPaymentMethodsList, + mockHandler, + callbackSpy + ); + + expect(callbackSpy).toHaveBeenCalled(); + }); + + it('Should remove event listener', () => { + const spy = spyOn(window, 'removeEventListener'); + + const callbackSpy = jasmine.createSpy(); + postMessagesClient.init(recipient, recipientUrl); + const removeListener = postMessagesClient.listen( + EventName.getPaymentMethodsList, + mockHandler, + callbackSpy + ); + removeListener(); + + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/src/core/post-messages-client/post-messages-client.ts b/src/core/post-messages-client/post-messages-client.ts new file mode 100644 index 00000000..1290b3d5 --- /dev/null +++ b/src/core/post-messages-client/post-messages-client.ts @@ -0,0 +1,79 @@ +import { Handler } from './handler.type'; +import { Message } from '../message.interface'; +import { singleton } from 'tsyringe'; +import { EventName } from '../event-name.enum'; +import { isEventMessage } from '../guards/event-message.guard'; + +@singleton() +export class PostMessagesClient { + private recipientUrl!: string; + private recipient!: HTMLIFrameElement; + private get noRecipient(): boolean { + return !(this.recipientUrl && this.recipient); + } + + public constructor(private readonly window: Window) {} + + public init(recipient: HTMLIFrameElement, recipientUrl: string): void { + this.recipient = recipient; + this.recipientUrl = recipientUrl; + } + + public async send(msg: Message, handler: Handler): Promise { + if (this.noRecipient) { + throw new Error('No recipient for post messages.'); + } + + return new Promise((resolve) => { + const handlerWrapper = (message: MessageEvent): void => { + if (this.isSameOrigin(message.origin)) { + const data = JSON.parse(message.data); + const handledData: { isHandled: boolean; value?: T } | null = + handler(data); + if (handledData) { + this.window.removeEventListener('message', handlerWrapper); + if (handledData.value) { + resolve(handledData.value); + return; + } + resolve(); + } + } + }; + + this.window.addEventListener('message', handlerWrapper); + this.sendMessage(msg); + }); + } + + public listen( + eventName: EventName, + handler: Handler, + callback: (value?: T) => void, + ): () => void { + const handlerWrapper = (message: MessageEvent): void => { + if (this.isSameOrigin(message.origin)) { + const data = JSON.parse(message.data); + if (isEventMessage(data) && data.name === eventName) { + const handledData = handler(data); + callback(handledData?.value); + } + } + }; + + this.window.addEventListener('message', handlerWrapper); + return () => this.window.removeEventListener('message', handlerWrapper); + } + + private sendMessage(message: Message): void { + this.recipient.contentWindow?.postMessage( + JSON.stringify(message), + this.recipientUrl, + ); + } + + private isSameOrigin(messageOrigin: string): boolean { + const recipientOrigin = new URL(this.recipientUrl).origin; + return messageOrigin === recipientOrigin; + } +} diff --git a/src/core/saved-method.interface.ts b/src/core/saved-method.interface.ts new file mode 100644 index 00000000..d1c7641c --- /dev/null +++ b/src/core/saved-method.interface.ts @@ -0,0 +1,19 @@ +export interface SavedMethod { + currency: string; + form: { paymentSid: string }; + id: number; + isSelected?: boolean; + isBabkaPay?: boolean; + name: string; + pid: number; + psName: string; + recurrentType: string; + replaced: boolean; + type: string; + cardExpiryDate?: { + month: string; + year: string; + }; + iconName: string | null; + partner?: string | null; +} diff --git a/src/core/spy/form-spy/form-spy.spec.ts b/src/core/spy/form-spy/form-spy.spec.ts new file mode 100644 index 00000000..2309506c --- /dev/null +++ b/src/core/spy/form-spy/form-spy.spec.ts @@ -0,0 +1,49 @@ +import 'reflect-metadata'; +import { container } from 'tsyringe'; +import { FormSpy } from './form-spy'; + +class Observer { + public formInitHandler(): void { + return; + } +} + +describe('FormSpy', () => { + let formSpy: FormSpy; + + beforeEach(() => { + container.clearInstances(); + formSpy = container.createChildContainer().resolve(FormSpy); + }); + + it('Should return true for init form', () => { + formSpy.formWasInit = true; + expect(formSpy.formWasInit).toBeTrue(); + }); + + it('Should call formWasInitHandler', () => { + const observer = new Observer(); + const spy = spyOn(observer, 'formInitHandler'); + formSpy.listenFormInit(observer.formInitHandler); + formSpy.formWasInit = true; + expect(spy).toHaveBeenCalled(); + }); + + it('Should call formWasInitHandler twice', () => { + const observer = new Observer(); + const spy = spyOn(observer, 'formInitHandler'); + formSpy.listenFormInit(observer.formInitHandler); + formSpy.formWasInit = true; + formSpy.formWasInit = true; + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('Should call appWasInitHandler once', () => { + const observer = new Observer(); + const spy = spyOn(observer, 'formInitHandler'); + formSpy.listenFormInit(observer.formInitHandler); + formSpy.formWasInit = true; + formSpy.formWasInit = false; + expect(spy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/core/spy/form-spy/form-spy.ts b/src/core/spy/form-spy/form-spy.ts new file mode 100644 index 00000000..7af2e0ad --- /dev/null +++ b/src/core/spy/form-spy/form-spy.ts @@ -0,0 +1,36 @@ +import { singleton } from 'tsyringe'; +import { Field } from '../../form/field.interface'; + +@singleton() +export class FormSpy { + private _formWasInit = false; + private _fields?: Field[]; + private readonly _callbacks: Array<() => void> = []; + + public set formWasInit(value: boolean) { + this._formWasInit = value; + if (value) { + this.formWasInitHandler(); + } + } + + public get formWasInit(): boolean { + return this._formWasInit; + } + + public set formFields(fields: Field[] | undefined) { + this._fields = fields; + } + + public get formFields(): Field[] | undefined { + return this._fields; + } + + public listenFormInit(callback: () => void): void { + this._callbacks.push(callback); + } + + private formWasInitHandler(): void { + this._callbacks.forEach((callback) => callback()); + } +} diff --git a/src/core/spy/headless-checkout-spy/headless-checkout-spy.spec.ts b/src/core/spy/headless-checkout-spy/headless-checkout-spy.spec.ts new file mode 100644 index 00000000..3d6b3e55 --- /dev/null +++ b/src/core/spy/headless-checkout-spy/headless-checkout-spy.spec.ts @@ -0,0 +1,51 @@ +import 'reflect-metadata'; +import { container } from 'tsyringe'; +import { HeadlessCheckoutSpy } from './headless-checkout-spy'; + +class Observer { + public appInitHandler(): void { + return; + } +} + +describe('HeadlessCheckoutSpy', () => { + let headlessCheckoutSpy: HeadlessCheckoutSpy; + + beforeEach(() => { + container.clearInstances(); + headlessCheckoutSpy = container + .createChildContainer() + .resolve(HeadlessCheckoutSpy); + }); + + it('Should return true for init app state', () => { + headlessCheckoutSpy.appWasInit = true; + expect(headlessCheckoutSpy.appWasInit).toBeTrue(); + }); + + it('Should call appWasInitHandler', () => { + const observer = new Observer(); + const spy = spyOn(observer, 'appInitHandler'); + headlessCheckoutSpy.listenAppInit(observer.appInitHandler); + headlessCheckoutSpy.appWasInit = true; + expect(spy).toHaveBeenCalled(); + }); + + it('Should call appWasInitHandler once', () => { + const observer = new Observer(); + const spy = spyOn(observer, 'appInitHandler'); + headlessCheckoutSpy.listenAppInit(observer.appInitHandler); + headlessCheckoutSpy.appWasInit = true; + headlessCheckoutSpy.appWasInit = true; + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('Should call appWasInitHandler once', () => { + const observer = new Observer(); + const spy = spyOn(observer, 'appInitHandler'); + headlessCheckoutSpy.listenAppInit(observer.appInitHandler); + headlessCheckoutSpy.appWasInit = true; + headlessCheckoutSpy.appWasInit = false; + expect(spy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/core/spy/headless-checkout-spy/headless-checkout-spy.ts b/src/core/spy/headless-checkout-spy/headless-checkout-spy.ts new file mode 100644 index 00000000..4a3f6c68 --- /dev/null +++ b/src/core/spy/headless-checkout-spy/headless-checkout-spy.ts @@ -0,0 +1,29 @@ +import { singleton } from 'tsyringe'; + +@singleton() +export class HeadlessCheckoutSpy { + private _appWasInit = false; + private readonly _callbacks: Array<() => void> = []; + + public set appWasInit(value: boolean) { + if (this._appWasInit === value) { + return; + } + this._appWasInit = value; + if (value) { + this.appWasInitHandler(); + } + } + + public get appWasInit(): boolean { + return this._appWasInit; + } + + public listenAppInit(callback: () => void): void { + this._callbacks.push(callback); + } + + private appWasInitHandler(): void { + this._callbacks.forEach((callback) => callback()); + } +} diff --git a/src/core/status/form-status.enum.ts b/src/core/status/form-status.enum.ts new file mode 100644 index 00000000..5d3c0481 --- /dev/null +++ b/src/core/status/form-status.enum.ts @@ -0,0 +1,5 @@ +export enum FormStatus { + undefined = 'undefined', + pending = 'pending', + active = 'active', +} diff --git a/src/core/status/status-finance-info.interface.ts b/src/core/status/status-finance-info.interface.ts new file mode 100644 index 00000000..4138db0b --- /dev/null +++ b/src/core/status/status-finance-info.interface.ts @@ -0,0 +1,7 @@ +export interface StatusFinanceInfo { + [key: string]: { + key?: string; + pref?: string; + value?: string; + }; +} diff --git a/src/core/status/status.enum.ts b/src/core/status/status.enum.ts new file mode 100644 index 00000000..5556c5ea --- /dev/null +++ b/src/core/status/status.enum.ts @@ -0,0 +1,10 @@ +export enum StatusEnum { + created = 'created', + awaiting = 'awaiting', + processing = 'processing', + authorized = 'authorized', + held = 'held', + done = 'done', + error = 'error', + canceled = 'canceled', +} diff --git a/src/core/status/status.interface.ts b/src/core/status/status.interface.ts new file mode 100644 index 00000000..cbab696a --- /dev/null +++ b/src/core/status/status.interface.ts @@ -0,0 +1,63 @@ +import { StatusFinanceInfo } from './status-finance-info.interface'; +import { StatusEnum } from './status.enum'; +import { TitleClass } from './title-class.enum'; + +export interface Status { + statusState: StatusEnum; + statusMessage: string; + group: string; + postMessageStatus?: string; + virtualCurrencyAmount?: number; + email?: string; + financeInfo?: StatusFinanceInfo; + isCancelUser?: boolean; + backUrlSettings?: { + backUrl: string; + backUrlAction?: string; + backUrlCaption: string; + returnRegion: string; + showBackButton?: boolean; + }; + additionalBackButton?: { + link: string; + action: string; + label: string; + region: string; + isEnabled: boolean; + }; + needToCheck?: boolean; + autoRedirect?: { time: number }; + userReturnStatus?: string; + discount?: number | null; + userId?: string; + invoice?: number; + pid?: number; + projectAmount?: { + amount: string; + currency: string; + }; + titleClass?: TitleClass; + isSuccess?: boolean; + isCanceled?: boolean; + paymentCountryIso?: string; + order?: { + lineitems?: Array<{ + name: string; + quantity: number; + currency: string; + amount: number; + amount_without_discount: number; + indirect_tax_rate: number; + }>; + shipping?: { + currency: string; + amount: number; + }; + contained_taxes?: { + sales_tax?: boolean; + user_vat?: boolean; + }; + }; + isSavePaymentAccount?: boolean; + autoCancellation: boolean; +} diff --git a/src/core/status/title-class.enum.ts b/src/core/status/title-class.enum.ts new file mode 100644 index 00000000..8d5ff895 --- /dev/null +++ b/src/core/status/title-class.enum.ts @@ -0,0 +1,5 @@ +export enum TitleClass { + done = 'done', + cancel = 'cancel', + cancelUser = 'canceluser', +} diff --git a/src/core/user-balance.interface.ts b/src/core/user-balance.interface.ts new file mode 100644 index 00000000..9b5398e5 --- /dev/null +++ b/src/core/user-balance.interface.ts @@ -0,0 +1,4 @@ +export interface UserBalance { + isEnoughUserBalance: boolean; + isEnoughUserRecurrentBalance: boolean; +} diff --git a/src/core/validators/locale/locale.validator.spec.ts b/src/core/validators/locale/locale.validator.spec.ts new file mode 100644 index 00000000..6d3b6d0a --- /dev/null +++ b/src/core/validators/locale/locale.validator.spec.ts @@ -0,0 +1,17 @@ +import { Lang } from '../../../core/i18n/lang.enum'; +import { localeValidator } from './locale.validator'; +import { LegacyLang } from '../../i18n/legacy-lang.enum'; + +describe('localeValidator', () => { + it('Should map ZH_HANS into CN locale', () => { + expect(localeValidator(LegacyLang.ZH_HANS)).toEqual(Lang.CN); + }); + + it('Should map ZH_HANT into CN locale', () => { + expect(localeValidator(LegacyLang.ZH_HANT)).toEqual(Lang.CN); + }); + + it('Should not map other locales', () => { + expect(localeValidator(Lang.EN)).toEqual(Lang.EN); + }); +}); diff --git a/src/core/validators/locale/locale.validator.ts b/src/core/validators/locale/locale.validator.ts new file mode 100644 index 00000000..0a956c9d --- /dev/null +++ b/src/core/validators/locale/locale.validator.ts @@ -0,0 +1,15 @@ +import { Lang } from '../../i18n/lang.enum'; +import { LegacyLang } from '../../i18n/legacy-lang.enum'; + +const specialLocaleMap = new Map([ + [LegacyLang.ZH_HANS, Lang.CN], + [LegacyLang.ZH_HANT, Lang.CN], +]); + +export const localeValidator = (locale: LegacyLang | Lang): Lang => { + if (specialLocaleMap.has(locale as LegacyLang)) { + return specialLocaleMap.get(locale as LegacyLang)!; + } + + return locale as Lang; +}; diff --git a/src/core/web-components/lit-web-component.abstract.ts b/src/core/web-components/lit-web-component.abstract.ts new file mode 100644 index 00000000..b8bffe51 --- /dev/null +++ b/src/core/web-components/lit-web-component.abstract.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { FormLoader } from '../form/form-loader'; +import { container } from 'tsyringe'; +import { createFinishLoadingEvent } from '../../shared/loading-state/dispatch-finish-loading-event.function'; +import { isLoadingCssClassName } from '../../shared/loading-state/is-loading-css-class-name.const'; +import { html, LitElement, TemplateResult } from 'lit'; + +export abstract class LitWebComponentAbstract extends LitElement { + protected formLoader: FormLoader = container.resolve(FormLoader); + + protected eventListeners: Array<{ + element: Element; + eventType: string; + listener(event: Event): void; + }> = []; + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this.removeAllEventListeners(); + } + + public createRenderRoot(): LitElement { + return this; + } + + protected abstract render(): void; + + protected renderByCondition( + condition: boolean, + template: TemplateResult<1>, + defaultValue: TemplateResult<1> = html``, + ): TemplateResult<1> { + return condition ? template : defaultValue; + } + + protected addEventListenerToElement( + element: Element, + eventType: string, + listener: (event: Event) => void, + ): void { + element.addEventListener(eventType, listener); + this.eventListeners.push({ element, eventType, listener }); + } + + protected removeAllEventListeners(): void { + this.eventListeners.forEach((item) => { + item.element.removeEventListener(item.eventType, item.listener); + }); + this.eventListeners = []; + } + + protected getJsonOrNull(data: string): unknown | null { + try { + return JSON.parse(data); + } catch (err: unknown) { + return null; + } + } + + protected finishLoadingFormControlHandler(componentName: string): void { + this.formLoader.setFieldLoaded(componentName); + } + + protected startLoadingComponentHandler(): void { + this.classList.add(isLoadingCssClassName); + } + + protected finishLoadingComponentHandler(componentName: string): void { + this.classList.remove(isLoadingCssClassName); + this.dispatchFinishLoadingEvent(componentName); + } + + protected dispatchFinishLoadingEvent(componentName: string): void { + this.dispatchEvent(createFinishLoadingEvent(componentName)); + this.formLoader.setFieldLoaded(componentName); + } +} diff --git a/src/core/web-components/secure-component/lit-secure-component.abstract.ts b/src/core/web-components/secure-component/lit-secure-component.abstract.ts new file mode 100644 index 00000000..1a22134c --- /dev/null +++ b/src/core/web-components/secure-component/lit-secure-component.abstract.ts @@ -0,0 +1,16 @@ +import { headlessCheckoutAppUrl } from '../../../features/headless-checkout/environment'; +import { LitWebComponentAbstract } from '../lit-web-component.abstract'; +import { property } from 'lit/decorators.js'; + +export abstract class LitSecureComponentAbstract extends LitWebComponentAbstract { + @property({ attribute: false }) + protected componentName: string | null = null; + + protected getSecureHtml(): string { + if (!this.componentName) { + throw new Error('Component name is required'); + } + + return ``; + } +} diff --git a/src/core/web-components/secure-component/secure-component.abstract.ts b/src/core/web-components/secure-component/secure-component.abstract.ts new file mode 100644 index 00000000..fa8e35d2 --- /dev/null +++ b/src/core/web-components/secure-component/secure-component.abstract.ts @@ -0,0 +1,14 @@ +import { WebComponentAbstract } from '../web-component.abstract'; +import { headlessCheckoutAppUrl } from '../../../features/headless-checkout/environment'; + +export abstract class SecureComponentAbstract extends WebComponentAbstract { + protected componentName: string | null = null; + + protected getSecureHtml(): string { + if (!this.componentName) { + throw new Error('Component name is required'); + } + + return ``; + } +} diff --git a/src/core/web-components/web-component-tag-name.enum.ts b/src/core/web-components/web-component-tag-name.enum.ts new file mode 100644 index 00000000..11a98ce7 --- /dev/null +++ b/src/core/web-components/web-component-tag-name.enum.ts @@ -0,0 +1,23 @@ +export enum WebComponentTagName { + TextComponent = 'psdk-text', + SubmitButtonComponent = 'psdk-submit-button', + DefaultSubmitButtonComponent = 'psdk-default-submit-button', + PaymentMethodsComponent = 'psdk-payment-methods', + SavedMethodsComponent = 'psdk-saved-methods', + PriceTextComponent = 'psdk-price-text', + FinanceDetailsComponent = 'psdk-finance-details', + TotalComponent = 'psdk-total', + LegalComponent = 'psdk-legal', + PaymentFormComponent = 'psdk-payment-form', + StatusComponent = 'psdk-status', + PhoneComponent = 'psdk-phone', + CardNumberComponent = 'psdk-card-number', + ThreeDsComponent = 'psdk-3ds', + SelectComponent = 'psdk-select', + CheckboxComponent = 'psdk-checkbox', + UserBalanceComponent = 'psdk-user-balance', + PaymentFormMessages = 'psdk-payment-form-messages', + GooglePayButtonComponent = 'psdk-google-pay-button', + ApplePayComponent = 'psdk-apple-pay', + QrCodeComponent = 'psdk-qr-code', +} diff --git a/src/core/web-components/web-component.abstract.ts b/src/core/web-components/web-component.abstract.ts new file mode 100644 index 00000000..b8cbf46f --- /dev/null +++ b/src/core/web-components/web-component.abstract.ts @@ -0,0 +1,104 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { FormLoader } from '../form/form-loader'; +import { container } from 'tsyringe'; +import { createFinishLoadingEvent } from '../../shared/loading-state/dispatch-finish-loading-event.function'; +import { isLoadingCssClassName } from '../../shared/loading-state/is-loading-css-class-name.const'; + +export abstract class WebComponentAbstract extends HTMLElement { + protected formLoader!: FormLoader; + + protected eventListeners: Array<{ + element: Element; + eventType: string; + listener(event: Event): void; + }> = []; + + public constructor() { + super(); + this.formLoader = container.resolve(FormLoader); + } + + protected abstract getHtml(): string; + + protected connectedCallback(): void { + this.render(); + } + + protected disconnectedCallback(): void { + this.removeAllEventListeners(); + } + + protected addEventListenerToElement( + element: Element, + eventType: string, + listener: (event: Event) => void, + ): void { + element.addEventListener(eventType, listener); + this.eventListeners.push({ element, eventType, listener }); + } + + protected removeAllEventListeners(): void { + this.eventListeners.forEach((item) => { + item.element.removeEventListener(item.eventType, item.listener); + }); + this.eventListeners = []; + } + + protected render(): void { + this.innerHTML = this.getHtml(); + } + + protected static get observedAttributes(): string[] { + return []; + } + + protected attributeChangedCallback( + name?: string, + oldValue?: string, + newValue?: string, + ): void { + this.render(); + } + + protected getNumberAttribute(name: string): number | null { + const stringValue = this.getAttribute(name); + + if (!stringValue) { + return null; + } + + const numberValue = parseFloat(stringValue); + + if (isNaN(numberValue)) { + return null; + } + + return numberValue; + } + + protected getJsonOrNull(data: string): unknown | null { + try { + return JSON.parse(data); + } catch (err: unknown) { + return null; + } + } + + protected finishLoadingFormControlHandler(componentName: string): void { + this.formLoader.setFieldLoaded(componentName); + } + + protected startLoadingComponentHandler(): void { + this.classList.add(isLoadingCssClassName); + } + + protected finishLoadingComponentHandler(componentName: string): void { + this.classList.remove(isLoadingCssClassName); + this.dispatchFinishLoadingEvent(componentName); + } + + protected dispatchFinishLoadingEvent(componentName: string): void { + this.dispatchEvent(createFinishLoadingEvent(componentName)); + this.formLoader.setFieldLoaded(componentName); + } +} diff --git a/src/core/web-components/web-components.map.ts b/src/core/web-components/web-components.map.ts new file mode 100644 index 00000000..a382e717 --- /dev/null +++ b/src/core/web-components/web-components.map.ts @@ -0,0 +1,49 @@ +import { TextComponent } from '../../features/headless-checkout/web-components/text-component/text.component'; +import { SubmitButtonComponent } from '../../features/headless-checkout/web-components/submit-button/submit-button.component'; +import { PaymentMethodsComponent } from '../../features/headless-checkout/web-components/payment-methods/payment-methods.component'; +import { LegalComponent } from '../../features/headless-checkout/web-components/legal/legal.component'; +import { StatusComponent } from '../../features/headless-checkout/web-components/status/status.component'; +import { FinanceDetailsComponent } from '../../features/headless-checkout/web-components/finance-details/finance-details.component'; +import { PriceTextComponent } from '../../features/headless-checkout/web-components/finance-details/price-text/price-text.component'; +import { WebComponentTagName } from './web-component-tag-name.enum'; +import { PaymentFormComponent } from '../../features/headless-checkout/web-components/payment-form/payment-form.component'; +import { CardNumberComponent } from '../../features/headless-checkout/web-components/card-number/card-number.component'; +import { ThreeDsComponent } from '../../features/headless-checkout/web-components/three-ds/three-ds.component'; +import { PhoneComponent } from '../../features/headless-checkout/web-components/phone-component/phone.component'; +import { SelectComponent } from '../../features/headless-checkout/web-components/select/select.component'; +import { CheckboxComponent } from '../../features/headless-checkout/web-components/checkbox/checkbox.component'; +import { SavedMethodsComponent } from '../../features/headless-checkout/web-components/saved-methods/saved-methods.component'; +import { UserBalanceComponent } from '../../features/headless-checkout/web-components/user-balance-component/user-balance-component'; +import { PaymentFormMessagesComponent } from '../../features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component'; +import { GooglePayButtonComponent } from '../../features/headless-checkout/web-components/pages/google-pay/google-pay-button.component'; +import { QrCodeComponent } from '../../features/headless-checkout/web-components/qr-code/qr-code.component'; +import { ApplePayComponent } from '../../features/headless-checkout/web-components/apple-pay/apple-pay.component'; +import { DefaultSubmitButtonComponent } from '../../features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component'; +import { TotalComponent } from '../../features/headless-checkout/web-components/finance-details/total.component'; + +export const webComponents: { + [key in WebComponentTagName]: CustomElementConstructor; +} = { + [WebComponentTagName.TextComponent]: TextComponent, + [WebComponentTagName.SubmitButtonComponent]: SubmitButtonComponent, + [WebComponentTagName.PaymentMethodsComponent]: PaymentMethodsComponent, + [WebComponentTagName.SavedMethodsComponent]: SavedMethodsComponent, + [WebComponentTagName.PriceTextComponent]: PriceTextComponent, + [WebComponentTagName.FinanceDetailsComponent]: FinanceDetailsComponent, + [WebComponentTagName.TotalComponent]: TotalComponent, + [WebComponentTagName.LegalComponent]: LegalComponent, + [WebComponentTagName.StatusComponent]: StatusComponent, + [WebComponentTagName.PaymentFormComponent]: PaymentFormComponent, + [WebComponentTagName.CardNumberComponent]: CardNumberComponent, + [WebComponentTagName.ThreeDsComponent]: ThreeDsComponent, + [WebComponentTagName.PhoneComponent]: PhoneComponent, + [WebComponentTagName.SelectComponent]: SelectComponent, + [WebComponentTagName.CheckboxComponent]: CheckboxComponent, + [WebComponentTagName.UserBalanceComponent]: UserBalanceComponent, + [WebComponentTagName.PaymentFormMessages]: PaymentFormMessagesComponent, + [WebComponentTagName.GooglePayButtonComponent]: GooglePayButtonComponent, + [WebComponentTagName.QrCodeComponent]: QrCodeComponent, + [WebComponentTagName.ApplePayComponent]: ApplePayComponent, + [WebComponentTagName.DefaultSubmitButtonComponent]: + DefaultSubmitButtonComponent, +}; diff --git a/src/core/xps-boolean.enum.ts b/src/core/xps-boolean.enum.ts new file mode 100644 index 00000000..9eb861c7 --- /dev/null +++ b/src/core/xps-boolean.enum.ts @@ -0,0 +1,4 @@ +export const enum XpsBoolean { + true = '1', + false = '0', +} diff --git a/src/features/headless-checkout/environment.ts b/src/features/headless-checkout/environment.ts new file mode 100644 index 00000000..c4d845a6 --- /dev/null +++ b/src/features/headless-checkout/environment.ts @@ -0,0 +1,5 @@ +export const headlessCheckoutAppUrl = + 'https://secure.xsolla.com/headless-checkout'; + +export const cdnIconsUrl = + 'https://cdn.xsolla.net/headless-checkout-prod/assets/icons'; diff --git a/src/features/headless-checkout/headless-checkout.instance.ts b/src/features/headless-checkout/headless-checkout.instance.ts new file mode 100644 index 00000000..29c627f6 --- /dev/null +++ b/src/features/headless-checkout/headless-checkout.instance.ts @@ -0,0 +1,5 @@ +import { container } from 'tsyringe'; +import { HeadlessCheckout } from './headless-checkout'; + +container.register(Window, { useValue: window }); +export const headlessCheckout = container.resolve(HeadlessCheckout); diff --git a/src/features/headless-checkout/headless-checkout.spec.ts b/src/features/headless-checkout/headless-checkout.spec.ts new file mode 100644 index 00000000..c8f367f6 --- /dev/null +++ b/src/features/headless-checkout/headless-checkout.spec.ts @@ -0,0 +1,250 @@ +import { container } from 'tsyringe'; +import { HeadlessCheckout } from './headless-checkout'; +import { PostMessagesClient } from '../../core/post-messages-client/post-messages-client'; +import { EventName } from '../../core/event-name.enum'; +import { Message } from '../../core/message.interface'; +import { Handler } from '../../core/post-messages-client/handler.type'; +import { LocalizeService } from '../../core/i18n/localize.service'; +import { getFinanceDetailsHandler } from './post-messages-handlers/get-finance-details.handler'; +import { FormStatus } from '../../core/status/form-status.enum'; +import { noopStub } from '../../tests/stubs/noop.stub'; +import { headlessCheckoutAppUrl } from './environment'; +import { ThemesLoader } from '../../core/customization/themes-loader'; + +const mockMessage: Message = { + name: EventName.initPayment, + data: {}, +}; + +const mockHandler: Handler = (): null => { + return null; +}; + +class MockIframeElement { + public width = ''; + public height = ''; + public style = { border: '' }; + + public constructor() { + setTimeout(() => this.onload()); + } + + public onload(): void { + return; + } +} + +class CustomElementMock extends HTMLElement {} + +describe('HeadlessCheckout', () => { + let windowService: Window; + let headlessCheckout: HeadlessCheckout; + let postMessagesClient: PostMessagesClient; + let localizeService: LocalizeService; + let themesLoader: ThemesLoader; + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const stub = (): void => {}; + + beforeEach(() => { + windowService = window; + spyOn(windowService, 'addEventListener').and.callFake( + (name: string, handlerWrapper: EventListenerOrEventListenerObject) => { + (handlerWrapper as (message: MessageEvent) => void)({ + origin: headlessCheckoutAppUrl, + data: JSON.stringify({ name: EventName.isReady }), + } as MessageEvent); + }, + ); + postMessagesClient = { + init: stub, + send: stub, + listen: stub, + } as unknown as PostMessagesClient; + + localizeService = { + initDictionaries: async () => Promise.resolve(), + } as unknown as LocalizeService; + + themesLoader = { + getTheme: () => 'body { margin: 0;}', + } as unknown as ThemesLoader; + + container.clearInstances(); + + headlessCheckout = container + .createChildContainer() + .register(Window, { useValue: windowService }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }) + .register(LocalizeService, { + useValue: localizeService, + }) + .register(ThemesLoader, { + useValue: themesLoader, + }) + .registerSingleton(HeadlessCheckout) + .resolve(HeadlessCheckout); + }); + + it('Should init', async () => { + const spy = spyOn(postMessagesClient, 'init'); + spyOn(windowService.document.body, 'appendChild'); + spyOn(windowService.document, 'createElement').and.callFake( + () => new MockIframeElement() as unknown as HTMLIFrameElement, + ); + await headlessCheckout.init({ isWebview: false }); + expect(spy).toHaveBeenCalled(); + }); + + it('Should init localization', () => { + const localizeSpy = spyOn( + localizeService, + 'initDictionaries', + ).and.resolveTo(); + + void headlessCheckout.init({ isWebview: false }); + + expect(localizeSpy).toHaveBeenCalled(); + }); + + it('Should send post message', async () => { + const spy = spyOn(postMessagesClient, 'send').and.stub(); + await headlessCheckout.events.send(mockMessage, mockHandler); + expect(spy).toHaveBeenCalled(); + }); + + it('Should set event handler', () => { + const spy = spyOn(postMessagesClient, 'listen'); + headlessCheckout.events.onCoreEvent( + EventName.initPayment, + mockHandler, + stub, + ); + expect(spy).toHaveBeenCalled(); + }); + + it('Should start listen errors on init', async () => { + const spy = spyOn(postMessagesClient, 'listen'); + spyOn(windowService.document.body, 'appendChild'); + spyOn(windowService.document, 'createElement').and.callFake( + () => new MockIframeElement() as unknown as HTMLIFrameElement, + ); + + await headlessCheckout.init({ isWebview: false }); + expect(spy).toHaveBeenCalled(); + }); + + it('Should setToken', async () => { + const spy = spyOn(postMessagesClient, 'send').and.stub(); + await headlessCheckout.setToken('token'); + expect(spy).toHaveBeenCalled(); + }); + + it('Should set secure component styles', async () => { + const spy = spyOn(postMessagesClient, 'send'); + await headlessCheckout.setSecureComponentStyles('styles'); + expect(spy).toHaveBeenCalled(); + }); + + it('Should throw exception if empty token', async () => { + try { + await headlessCheckout.setToken(''); + } catch (e: unknown) { + expect(e).toEqual(new Error('Need correct token')); + } + }); + + it('Should getFinanceDetails', async () => { + const spy = spyOn(postMessagesClient, 'send').and.stub(); + await headlessCheckout.getFinanceDetails(); + expect(spy).toHaveBeenCalledWith( + { name: EventName.financeDetails }, + getFinanceDetailsHandler, + ); + }); + + it('Should getRegularMethods', async () => { + const spy = spyOn(postMessagesClient, 'send').and.stub(); + await headlessCheckout.getRegularMethods(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should getQuickMethods', async () => { + const spy = spyOn(postMessagesClient, 'send').and.stub(); + await headlessCheckout.getQuickMethods(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should getCombinedPaymentMethods', async () => { + const spy = spyOn(postMessagesClient, 'send').and.stub(); + await headlessCheckout.getCombinedPaymentMethods(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should correctly define web components', async () => { + const spy = spyOn(windowService.customElements, 'define'); + spyOn(windowService.customElements, 'get').and.returnValue(undefined); + spyOn(windowService.document.body, 'appendChild'); + spyOn(windowService.document, 'createElement').and.callFake( + () => new MockIframeElement() as unknown as HTMLIFrameElement, + ); + + await headlessCheckout.init({ isWebview: false }); + expect(spy).toHaveBeenCalled(); + }); + + it('Should web components not be redefined', async () => { + spyOn(windowService.customElements, 'get').and.returnValue( + CustomElementMock, + ); + spyOn(windowService.document.body, 'appendChild'); + spyOn(windowService.document, 'createElement').and.callFake( + () => new MockIframeElement() as unknown as HTMLIFrameElement, + ); + + const spy = spyOn(window.customElements, 'define'); + + await headlessCheckout.init({ isWebview: false }); + expect(spy).not.toHaveBeenCalled(); + }); + + it('Should getSavedMethods', async () => { + const spy = spyOn(postMessagesClient, 'send').and.stub(); + await headlessCheckout.getSavedMethods(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should getUserBalance', async () => { + const spy = spyOn(postMessagesClient, 'send').and.stub(); + await headlessCheckout.getUserBalance(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should have correct form status', async () => { + expect(headlessCheckout.form.getStatus()).toEqual(FormStatus.undefined); + + spyOn(postMessagesClient, 'send').and.callFake(async (msg, callback) => { + await Promise.resolve().then(() => callback(msg)); + return Promise.resolve(undefined); + }); + + const formInitPromise = headlessCheckout.form.init({ + paymentMethodId: 1380, + returnUrl: '', + }); + + expect(headlessCheckout.form.getStatus()).toEqual(FormStatus.pending); + + await formInitPromise.then(); + + expect(headlessCheckout.form.getStatus()).toEqual(FormStatus.active); + }); + + it('Should listen form fields status changed events', () => { + const spy = spyOn(postMessagesClient, 'listen'); + headlessCheckout.form.onFieldsStatusChange(noopStub); + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/src/features/headless-checkout/headless-checkout.ts b/src/features/headless-checkout/headless-checkout.ts new file mode 100644 index 00000000..1d56eb28 --- /dev/null +++ b/src/features/headless-checkout/headless-checkout.ts @@ -0,0 +1,436 @@ +import { singleton } from 'tsyringe'; +import { EventName } from '../../core/event-name.enum'; +import { LocalizeService } from '../../core/i18n/localize.service'; +import { Message } from '../../core/message.interface'; +import { PaymentMethod } from '../../core/payment-method.interface'; +import { Handler } from '../../core/post-messages-client/handler.type'; +import { PostMessagesClient } from '../../core/post-messages-client/post-messages-client'; +import { webComponents } from '../../core/web-components/web-components.map'; +import { SavedMethod } from '../../core/saved-method.interface'; +import { UserBalance } from '../../core/user-balance.interface'; +import { HeadlessCheckoutSpy } from '../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { FormConfiguration } from '../../core/form/form-configuration.interface'; +import { Form } from '../../core/form/form.interface'; +import { NextAction } from '../../core/actions/next-action.interface'; +import { FormSpy } from '../../core/spy/form-spy/form-spy'; +import { Status } from '../../core/status/status.interface'; +import { getErrorHandler } from './post-messages-handlers/error.handler'; +import { initFormHandler } from './post-messages-handlers/init-form.handler'; +import { getQuickMethodsHandler } from './post-messages-handlers/get-quick-methods.handler'; +import { setTokenHandler } from './post-messages-handlers/set-token.handler'; +import { getRegularMethodsHandler } from './post-messages-handlers/get-regular-methods.handler'; +import { getSavedMethodsHandler } from './post-messages-handlers/get-saved-methods.handler'; +import { getUserBalanceHandler } from './post-messages-handlers/get-user-balance.handler'; +import { nextActionHandler } from './post-messages-handlers/next-action.handler'; +import { Field } from '../../core/form/field.interface'; +import { getPaymentStatusHandler } from './post-messages-handlers/get-payment-status/get-payment-status.handler'; +import { headlessCheckoutAppUrl } from './environment'; +import { FinanceDetails } from '../../core/finance-details/finance-details.interface'; +import { getFinanceDetailsHandler } from './post-messages-handlers/get-finance-details.handler'; +import { FormStatus } from '../../core/status/form-status.enum'; +import { setSecureComponentStylesHandler } from './post-messages-handlers/set-secure-component-styles.handler'; +import { formFieldsStatusChangedHandler } from './post-messages-handlers/form-fields-status-changed.handler'; +import { FormFieldsStatus } from '../../core/form/form-fields-status.interface'; +import { getCombinedPaymentMethodsHandler } from './post-messages-handlers/get-combined-payment-methods.handler'; +import { CombinedPaymentMethods } from '../../core/combined-payment-methods.interface'; +import { ThemesLoader } from '../../core/customization/themes-loader'; +import { Themes } from '../../core/customization/themes.type'; +import { Lang } from '../../core/i18n/lang.enum'; +import { getCountryListHandler } from './post-messages-handlers/get-country-list.handler'; +import { CountryResponse } from '../../core/country-response.interface'; +import { FormLoader } from '../../core/form/form-loader'; + +@singleton() +export class HeadlessCheckout { + public events = { + /** + * Send public message + * @param msg Message to send + * @param handler Handler to process response data + * @returns promise that will be resolved once response is recieved + */ + send: async (msg: Message, handler: Handler): Promise => { + return this.postMessagesClient.send(msg, handler); + }, + + /** + * Add core event listener + * @param eventName Name of event to listen + * @param handler Handler to process response data + * @param callback Callback for event + * @return {Function} function to remove listener + */ + onCoreEvent: ( + eventName: EventName, + handler: Handler, + callback: (value?: T) => void, + ): (() => void) => { + return this.postMessagesClient.listen(eventName, handler, callback); + }, + }; + + public form = { + /** + * Initialize payment form + * @param configuration Form configuration + * @returns {Form} form details + */ + init: async (configuration: FormConfiguration): Promise => { + this._formConfiguration = configuration; + this.formStatus = FormStatus.pending; + + const msg: Message = { + name: EventName.initForm, + data: { + configuration, + }, + }; + + this.formSpy.formWasInit = false; + + return this.postMessagesClient.send(msg, (message) => + initFormHandler(message, (args?: unknown) => { + if (args) { + this.formSpy.formFields = (args as { fields: Field[] }).fields; + } + this.formSpy.formWasInit = true; + this.formStatus = FormStatus.active; + }), + ) as Promise; + }, + + onNextAction: (callbackFn: (nextAction: NextAction) => void): void => { + this.postMessagesClient.listen( + EventName.nextAction, + nextActionHandler, + (nextAction) => { + if (nextAction) { + callbackFn(nextAction); + } + }, + ); + }, + + onFieldsStatusChange: ( + callbackFn: (fieldsStatus: FormFieldsStatus) => void, + ): void => { + this.postMessagesClient.listen( + EventName.formFieldsStatusChanged, + formFieldsStatusChangedHandler, + (fieldsStatus) => { + if (fieldsStatus) { + callbackFn(fieldsStatus); + } + }, + ); + }, + + getStatus: (): FormStatus => { + if (this.formSpy.formWasInit) { + return FormStatus.active; + } + + return this.formStatus === FormStatus.pending + ? FormStatus.pending + : FormStatus.undefined; + }, + + activate: (): void => { + this.formStatus = FormStatus.active; + }, + setupAndAwaitFieldsLoading: async (fields: Field[]): Promise => + this.formLoader.setupAndAwaitFieldsLoading(fields), + }; + + public get formConfiguration(): FormConfiguration | undefined { + return this._formConfiguration; + } + + private formStatus: FormStatus = FormStatus.undefined; + private isWebView?: boolean; + private isSandbox?: boolean; + private theme?: string; + private coreIframe!: HTMLIFrameElement; + private errorsSubscription?: () => void; + private readonly headlessAppUrl = headlessCheckoutAppUrl; + private _formConfiguration?: FormConfiguration; + + public constructor( + private readonly window: Window, + private readonly postMessagesClient: PostMessagesClient, + private readonly localizeService: LocalizeService, + private readonly headlessCheckoutSpy: HeadlessCheckoutSpy, + private readonly formSpy: FormSpy, + private readonly themesLoader: ThemesLoader, + private readonly formLoader: FormLoader, + ) {} + + public async init(environment: { + isWebview?: boolean; + sandbox?: boolean; + theme?: Themes; + language?: Lang; + }): Promise { + this.isWebView = environment.isWebview; + this.isSandbox = environment.sandbox; + this.theme = environment.theme; + + await this.localizeService.initDictionaries(environment.language); + + this.postMessagesClient.init(this.coreIframe, this.headlessAppUrl); + await this.setupCoreIframe(); + this.defineComponents(); + + this.postMessagesClient.init(this.coreIframe, this.headlessAppUrl); + void this.setupSecureStyles(this.theme); + this.errorsSubscription = this.postMessagesClient.listen( + EventName.error, + getErrorHandler, + (error) => { + throw new Error(error); + }, + ); + } + + public destroy(): void { + this.destroyCoreIframe(); + this.errorsSubscription?.(); + } + + /** + * Initialize the payment with a token. + * Reset the form on initialization. + */ + public async setToken(token: string): Promise { + if (!token) { + throw new Error('Need correct token'); + } + + const msg: Message = { + name: EventName.initPayment, + data: { + configuration: { + token, + isWebView: this.isWebView, + sandbox: this.isSandbox, + }, + }, + }; + + return this.postMessagesClient.send(msg, (message) => + setTokenHandler(message, () => { + this.headlessCheckoutSpy.appWasInit = true; + }), + ); + } + + public async setSecureComponentStyles(styles: string): Promise { + return this.postMessagesClient.send( + { + name: EventName.setSecureComponentStyles, + data: styles, + }, + setSecureComponentStylesHandler, + ); + } + + /** + * Returns finance details for created payment + * @returns promise that returns finance details + */ + public async getFinanceDetails(): Promise { + const msg: Message = { + name: EventName.financeDetails, + }; + + return this.postMessagesClient.send( + msg, + getFinanceDetailsHandler, + ) as Promise; + } + + /** + * Returns available payment methods except quick methods + * @param country Country that quick methods should be loaded for. + * Country from token is used by default + * @returns promise that returns payment methods + */ + public async getRegularMethods(options?: { + country?: string; + isSaveMethodMode?: boolean; + }): Promise { + const msg: Message = { + name: EventName.getPaymentMethodsList, + data: { + country: options?.country, + isSaveMethodMode: options?.isSaveMethodMode, + }, + }; + + return this.postMessagesClient.send( + msg, + getRegularMethodsHandler, + ) as Promise; + } + + /** + * Returns available quick payment methods like ApplePay, GooglePay, etc. + * @param country Country that quick methods should be loaded for. + * Country from token is used by default + * @returns promise that returns payment methods + */ + public async getQuickMethods(country?: string): Promise { + const msg: Message = { + name: EventName.getPaymentQuickMethods, + data: { + country, + }, + }; + + return this.postMessagesClient.send( + msg, + getQuickMethodsHandler, + ) as Promise; + } + + /** + * Returns available payment methods, including quick payment options and saved payment methods. + */ + public async getCombinedPaymentMethods( + country?: string, + ): Promise { + const msg: Message = { + name: EventName.getCombinedPaymentMethods, + data: { + country, + }, + }; + + return this.postMessagesClient.send( + msg, + getCombinedPaymentMethodsHandler, + ) as Promise; + } + + /** + * Returns user’s saved methods. + */ + public async getSavedMethods(): Promise { + const msg: Message = { + name: EventName.getSavedMethods, + }; + + return this.postMessagesClient.send( + msg, + getSavedMethodsHandler, + ) as Promise; + } + + /** + * Returns a user’s balance. + */ + public async getUserBalance(): Promise { + const msg: Message = { + name: EventName.getUserBalance, + }; + + return this.postMessagesClient.send( + msg, + getUserBalanceHandler, + ) as Promise; + } + + /** + * Get the final payment status: Success or Error. + * Calling this method breaks the previous connection. + * Use the invoiceId argument after a return from the payment system. + * @throws UndefinedFormError if you did not pass invoiceId and the initialized form does not exist. + * @throws BreakConnectionError if the method is called again. + */ + public async getStatus(): Promise { + const msg: Message = { + name: EventName.getPaymentStatus, + data: { + url: this.window.location.href, + }, + }; + + return this.postMessagesClient.send(msg, (message) => + getPaymentStatusHandler(message), + ) as Promise; + } + + /** + * Returns available locales. + */ + public getAvailableLanguages(): Lang[] { + return this.localizeService.getAvailableLanguages(); + } + + /** + * Returns available countries. + */ + public async getCountryList(): Promise<{ + countryList: CountryResponse['countryList']; + currentCountry: string; + }> { + const msg: Message = { + name: EventName.getCountryList, + }; + + return this.postMessagesClient.send<{ + countryList: CountryResponse['countryList']; + currentCountry: string; + }>(msg, getCountryListHandler) as Promise<{ + countryList: CountryResponse['countryList']; + currentCountry: string; + }>; + } + + private async setupCoreIframe(): Promise { + this.coreIframe = this.window.document.createElement('iframe'); + this.coreIframe.width = '0px'; + this.coreIframe.height = '0px'; + this.coreIframe.style.border = 'none'; + this.coreIframe.style.position = 'absolute'; + this.coreIframe.src = `${this.headlessAppUrl}/core`; + this.coreIframe.name = 'core'; + this.window.document.body.appendChild(this.coreIframe); + return this.listenCoreIframeLoading(); + } + + private async listenCoreIframeLoading(): Promise { + return new Promise((resolve) => { + const handler = (event: MessageEvent): void => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (JSON.parse(event.data as string).name === EventName.isReady) { + resolve(); + this.window.removeEventListener('message', handler); + } + }; + this.window.addEventListener('message', handler); + }); + } + + private destroyCoreIframe(): void { + this.coreIframe.remove(); + } + + private defineComponents(): void { + Object.entries(webComponents).forEach(([tagName, component]) => { + if (this.window.customElements.get(tagName)) { + return; + } + + this.window.customElements.define(tagName, component); + }); + } + + private async setupSecureStyles(themeName?: string): Promise { + if (!themeName) { + return; + } + const themeStyles = this.themesLoader.getTheme(); + await this.setSecureComponentStyles(themeStyles); + } +} diff --git a/src/features/headless-checkout/post-messages-handlers/apple-pay/apple-pay-error.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/apple-pay/apple-pay-error.handler.spec.ts new file mode 100644 index 00000000..366d0b4f --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/apple-pay/apple-pay-error.handler.spec.ts @@ -0,0 +1,26 @@ +import { applePayErrorHandler } from './apple-pay-error.handler'; +import { Message } from '../../../../core/message.interface'; +import { EventName } from '../../../../core/event-name.enum'; + +const mockMessage: Message<{ error: string } | null | undefined> = { + name: EventName.applePayError, + data: { + error: 'error', + }, +}; + +describe('applePayErrorHandler', () => { + it('Should handle data', () => { + expect(applePayErrorHandler(mockMessage)).toEqual({ + isHandled: true, + value: { + error: 'error', + }, + }); + }); + it('Should return null', () => { + expect( + applePayErrorHandler({ name: EventName.getSavedMethods }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/apple-pay/apple-pay-error.handler.ts b/src/features/headless-checkout/post-messages-handlers/apple-pay/apple-pay-error.handler.ts new file mode 100644 index 00000000..0d0260bf --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/apple-pay/apple-pay-error.handler.ts @@ -0,0 +1,21 @@ +import { Handler } from '../../../../core/post-messages-client/handler.type'; +import { Message } from '../../../../core/message.interface'; +import { isApplePayErrorEventMessage } from '../../../../core/guards/apple-pay/apple-pay-error.guard'; + +export const applePayErrorHandler: Handler< + { error: string } | null | undefined +> = ( + message: Message +): { + isHandled: boolean; + value: { error: string } | null | undefined; +} | null => { + if (!isApplePayErrorEventMessage(message)) { + return null; + } + + return { + isHandled: true, + value: message.data, + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/apple-pay/open-apple-pay-page.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/apple-pay/open-apple-pay-page.handler.spec.ts new file mode 100644 index 00000000..64bff38d --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/apple-pay/open-apple-pay-page.handler.spec.ts @@ -0,0 +1,26 @@ +import { Message } from '../../../../core/message.interface'; +import { EventName } from '../../../../core/event-name.enum'; +import { openApplePayPageHandler } from './open-apple-pay-page.handler'; + +const mockMessage: Message<{ redirectUrl: string } | null | undefined> = { + name: EventName.openApplePayPage, + data: { + redirectUrl: 'url', + }, +}; + +describe('openApplePayPageHandler', () => { + it('Should handle data', () => { + expect(openApplePayPageHandler(mockMessage)).toEqual({ + isHandled: true, + value: { + redirectUrl: 'url', + }, + }); + }); + it('Should return null', () => { + expect( + openApplePayPageHandler({ name: EventName.getSavedMethods }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/apple-pay/open-apple-pay-page.handler.ts b/src/features/headless-checkout/post-messages-handlers/apple-pay/open-apple-pay-page.handler.ts new file mode 100644 index 00000000..886ce7e1 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/apple-pay/open-apple-pay-page.handler.ts @@ -0,0 +1,21 @@ +import { Handler } from '../../../../core/post-messages-client/handler.type'; +import { Message } from '../../../../core/message.interface'; +import { isOpenApplePayPageEventMessage } from '../../../../core/guards/apple-pay/open-apple-pay-page.guard'; + +export const openApplePayPageHandler: Handler< + { redirectUrl: string } | null | undefined +> = ( + message: Message +): { + isHandled: boolean; + value: { redirectUrl: string } | null | undefined; +} | null => { + if (!isOpenApplePayPageEventMessage(message)) { + return null; + } + + return { + isHandled: true, + value: message.data, + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/delete-saved-method.handler.ts b/src/features/headless-checkout/post-messages-handlers/delete-saved-method.handler.ts new file mode 100644 index 00000000..cec2dc4f --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/delete-saved-method.handler.ts @@ -0,0 +1,17 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { isDeleteSavedMethodMessage } from '../../../core/guards/delete-saved-method-message.guard'; + +export const deleteSavedMethodHandler: Handler = ( + message: Message, +): { isHandled: boolean; value: boolean } | null => { + if (!isDeleteSavedMethodMessage(message)) { + return null; + } + + const isDeleteSuccessful = !!message.data?.isDeleteSuccessful; + return { + isHandled: true, + value: isDeleteSuccessful, + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/error.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/error.handler.spec.ts new file mode 100644 index 00000000..1fd0f578 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/error.handler.spec.ts @@ -0,0 +1,30 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { getErrorHandler } from './error.handler'; + +const mockMessage: Message<{ error: string }> = { + name: EventName.error, + data: { error: 'error' }, +}; + +const emptyErrorMessage: Message<{ error: string }> = { + name: EventName.error, +}; + +describe('getErrorHandler', () => { + it('Should handle data', () => { + expect(getErrorHandler(mockMessage)).toEqual({ + isHandled: true, + value: 'error', + }); + }); + it('Should set default error text', () => { + expect(getErrorHandler(emptyErrorMessage)).toEqual({ + isHandled: true, + value: 'Unknown error', + }); + }); + it('Should return null', () => { + expect(getErrorHandler({ name: EventName.initPayment })).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/error.handler.ts b/src/features/headless-checkout/post-messages-handlers/error.handler.ts new file mode 100644 index 00000000..4df276d7 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/error.handler.ts @@ -0,0 +1,15 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { isErrorEventMessage } from '../../../core/guards/error-event-message.guard'; + +export const getErrorHandler: Handler = ( + message: Message +): { isHandled: boolean; value: string } | null => { + if (isErrorEventMessage(message)) { + return { + isHandled: true, + value: message.data?.error ?? 'Unknown error', + }; + } + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/finish-load-component.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/finish-load-component.handler.spec.ts new file mode 100644 index 00000000..eaa62087 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/finish-load-component.handler.spec.ts @@ -0,0 +1,29 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { getUserBalanceValueHandler } from './get-user-balance-value.handler'; +import { finishLoadComponentHandler } from './finish-load-component.handler'; + +const mockData = { + fieldName: 'fieldName', +}; + +const mockMessage: Message<{ fieldName: string } | null | undefined> = { + name: EventName.finishLoadComponent, + data: { + fieldName: 'fieldName', + }, +}; + +describe('finishLoadComponentHandler', () => { + it('Should handle data', () => { + expect(finishLoadComponentHandler(mockMessage)).toEqual({ + isHandled: true, + value: mockData, + }); + }); + it('Should return null', () => { + expect( + getUserBalanceValueHandler({ name: EventName.getSavedMethods }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/finish-load-component.handler.ts b/src/features/headless-checkout/post-messages-handlers/finish-load-component.handler.ts new file mode 100644 index 00000000..f6c62eae --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/finish-load-component.handler.ts @@ -0,0 +1,21 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { isFinishLoadComponentEventMessage } from '../../../core/guards/finish-load-component.guard'; + +export const finishLoadComponentHandler: Handler< + { fieldName: string } | null | undefined +> = ( + message: Message +): { + isHandled: boolean; + value: { fieldName: string } | null | undefined; +} | null => { + if (!isFinishLoadComponentEventMessage(message)) { + return null; + } + + return { + isHandled: true, + value: message.data, + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/form-fields-status-changed.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/form-fields-status-changed.handler.spec.ts new file mode 100644 index 00000000..050e121e --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/form-fields-status-changed.handler.spec.ts @@ -0,0 +1,26 @@ +import { FormFieldsStatus } from '../../../core/form/form-fields-status.interface'; +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { formFieldsStatusChangedHandler } from './form-fields-status-changed.handler'; + +const formFieldsStatus = {} as unknown as FormFieldsStatus; + +const mockMessage: Message = { + name: EventName.formFieldsStatusChanged, + data: formFieldsStatus, +}; + +describe('formFieldsStatusChangedHandler', () => { + it('Should handle formFieldsStatusChanged event', () => { + expect(formFieldsStatusChangedHandler(mockMessage)).toEqual({ + isHandled: true, + value: formFieldsStatus, + }); + }); + + it('Should not handle not formFieldsStatusChanged event', () => { + expect( + formFieldsStatusChangedHandler({ name: EventName.initPayment }), + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/form-fields-status-changed.handler.ts b/src/features/headless-checkout/post-messages-handlers/form-fields-status-changed.handler.ts new file mode 100644 index 00000000..662a2b18 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/form-fields-status-changed.handler.ts @@ -0,0 +1,17 @@ +import { FormFieldsStatus } from '../../../core/form/form-fields-status.interface'; +import { isFormFieldsStatusChangedMessage } from '../../../core/guards/form-fields-status-changed-message.guard'; +import { Message } from '../../../core/message.interface'; +import { Handler } from '../../../core/post-messages-client/handler.type'; + +export const formFieldsStatusChangedHandler: Handler = ( + message: Message, +): { isHandled: boolean; value?: FormFieldsStatus } | null => { + if (isFormFieldsStatusChangedMessage(message)) { + return { + isHandled: true, + value: message.data, + }; + } + + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-combined-payment-methods.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-combined-payment-methods.handler.ts new file mode 100644 index 00000000..17027baf --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-combined-payment-methods.handler.ts @@ -0,0 +1,20 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { isCombinedMethodsEventMessage } from '../../../core/guards/combined-payment-methods-event-message.guard'; +import { CombinedPaymentMethods } from '../../../core/combined-payment-methods.interface'; + +export const getCombinedPaymentMethodsHandler: Handler< + CombinedPaymentMethods +> = ( + message: Message, +): { isHandled: boolean; value: CombinedPaymentMethods } | null => { + if (!isCombinedMethodsEventMessage(message)) { + return null; + } + + const combinedPaymentMethods = message.data?.combinedPaymentMethods; + return { + isHandled: true, + value: combinedPaymentMethods as unknown as CombinedPaymentMethods, + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-country-list.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-country-list.handler.ts new file mode 100644 index 00000000..57bb757f --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-country-list.handler.ts @@ -0,0 +1,29 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { CountryResponse } from '../../../core/country-response.interface'; +import { isCountryListEventMessage } from '../../../core/guards/country-list-event-message.guard'; + +export const getCountryListHandler: Handler<{ + countryList: CountryResponse['countryList']; + currentCountry: string; +}> = ( + message: Message, +): { + isHandled: boolean; + value: { + countryList: CountryResponse['countryList']; + currentCountry: string; + }; +} | null => { + if (!isCountryListEventMessage(message)) { + return null; + } + + const countryList = message.data?.countryList ?? []; + const currentCountry = message.data?.currentCountry ?? ''; + + return { + isHandled: true, + value: { countryList, currentCountry }, + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-finance-details.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/get-finance-details.handler.spec.ts new file mode 100644 index 00000000..e495fd5d --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-finance-details.handler.spec.ts @@ -0,0 +1,26 @@ +import { EventName } from '../../../core/event-name.enum'; +import { FinanceDetails } from '../../../core/finance-details/finance-details.interface'; +import { Message } from '../../../core/message.interface'; +import { getFinanceDetailsHandler } from './get-finance-details.handler'; + +const financeDetails = {} as unknown as FinanceDetails; + +const mockMessage: Message = { + name: EventName.financeDetails, + data: financeDetails, +}; + +describe('getFinanceDetailsHandler', () => { + it('Should handle financeDetails event', () => { + expect(getFinanceDetailsHandler(mockMessage)).toEqual({ + isHandled: true, + value: financeDetails, + }); + }); + + it('Should not handle not financeDetails event', () => { + expect( + getFinanceDetailsHandler({ name: EventName.initPayment }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/get-finance-details.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-finance-details.handler.ts new file mode 100644 index 00000000..8ae0df3d --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-finance-details.handler.ts @@ -0,0 +1,16 @@ +import { FinanceDetails } from '../../../core/finance-details/finance-details.interface'; +import { isFinanceDetailsEventMessage } from '../../../core/guards/finance-details-message.guard'; +import { Message } from '../../../core/message.interface'; +import { Handler } from '../../../core/post-messages-client/handler.type'; + +export const getFinanceDetailsHandler: Handler = ( + message: Message +): { isHandled: boolean; value?: FinanceDetails | null } | null => { + if (isFinanceDetailsEventMessage(message)) { + return { + isHandled: true, + value: message.data, + }; + } + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-form-messages.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/get-form-messages.handler.spec.ts new file mode 100644 index 00000000..b3b06c31 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-form-messages.handler.spec.ts @@ -0,0 +1,24 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { getFormMessagesHandler } from './get-form-messages.handler'; + +const mockMessages: string[] = ['message']; + +const mockMessage: Message = { + name: EventName.formMessagesChanged, + data: ['message'], +}; + +describe('getFormMessagesHandler', () => { + it('Should handle data', () => { + expect(getFormMessagesHandler(mockMessage)).toEqual({ + isHandled: true, + value: mockMessages, + }); + }); + it('Should return null', () => { + expect( + getFormMessagesHandler({ name: EventName.getSavedMethods }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/get-form-messages.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-form-messages.handler.ts new file mode 100644 index 00000000..371da3cd --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-form-messages.handler.ts @@ -0,0 +1,15 @@ +import { Message } from '../../../core/message.interface'; +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { isFormMessagesEventMessage } from '../../../core/guards/form-messages.guard'; + +export const getFormMessagesHandler: Handler = ( + message: Message +): { isHandled: boolean; value?: string[] } | null => { + if (isFormMessagesEventMessage(message)) { + return { + isHandled: true, + value: message.data, + }; + } + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-legal-component-config.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/get-legal-component-config.handler.spec.ts new file mode 100644 index 00000000..b4632561 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-legal-component-config.handler.spec.ts @@ -0,0 +1,22 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { getLegalComponentConfigHandler } from './get-legal-component-config.handler'; +import { LegalComponentConfig } from '../web-components/legal/legal-component.config.interface'; + +const mockMessage: Message<{ config: LegalComponentConfig }> = { + name: EventName.getLegalComponentConfig, + data: { config: {} as unknown as LegalComponentConfig }, +}; +describe('getLegalComponentConfigHandler', () => { + it('Should handle data', () => { + expect(getLegalComponentConfigHandler(mockMessage)).toEqual({ + isHandled: true, + value: {} as unknown as LegalComponentConfig, + }); + }); + it('Should return null', () => { + expect( + getLegalComponentConfigHandler({ name: EventName.initPayment }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/get-legal-component-config.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-legal-component-config.handler.ts new file mode 100644 index 00000000..6e92c966 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-legal-component-config.handler.ts @@ -0,0 +1,21 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { EventName } from '../../../core/event-name.enum'; +import { LegalComponentConfig } from '../web-components/legal/legal-component.config.interface'; +import { isLegalConfigEventMessage } from '../../../core/guards/legal-config-event-message.guard'; + +export const getLegalComponentConfigHandler: Handler = ( + message: Message +): { isHandled: boolean; value: LegalComponentConfig } | null => { + if ( + isLegalConfigEventMessage(message) && + message.name === EventName.getLegalComponentConfig + ) { + const config = message.data?.config; + return { + isHandled: true, + value: config!, + }; + } + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-payment-status/get-payment-status.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/get-payment-status/get-payment-status.handler.spec.ts new file mode 100644 index 00000000..80a542c8 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-payment-status/get-payment-status.handler.spec.ts @@ -0,0 +1,21 @@ +import { EventName } from '../../../../core/event-name.enum'; +import { Message } from '../../../../core/message.interface'; +import { getPaymentStatusHandler } from './get-payment-status.handler'; + +const mockMessage: Message = { + name: EventName.getPaymentStatus, +}; +describe('getPaymentStatusHandler', () => { + it('should handle status', () => { + expect(getPaymentStatusHandler(mockMessage)).toEqual({ + isHandled: true, + value: undefined, + }); + }); + + it('should return null', () => { + expect( + getPaymentStatusHandler({ name: EventName.getPaymentMethodsList }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/get-payment-status/get-payment-status.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-payment-status/get-payment-status.handler.ts new file mode 100644 index 00000000..9b9b9c0f --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-payment-status/get-payment-status.handler.ts @@ -0,0 +1,17 @@ +import { Handler } from '../../../../core/post-messages-client/handler.type'; +import { Message } from '../../../../core/message.interface'; +import { Status } from '../../../../core/status/status.interface'; +import { isGetPaymentStatusMessage } from '../../../../core/guards/get-payment-status-message.guard'; + +export const getPaymentStatusHandler: Handler = ( + message: Message +): { isHandled: boolean; value?: Status } | null => { + if (!isGetPaymentStatusMessage(message)) { + return null; + } + + return { + isHandled: true, + value: message.data, + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-quick-methods.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/get-quick-methods.handler.spec.ts new file mode 100644 index 00000000..183d9b20 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-quick-methods.handler.spec.ts @@ -0,0 +1,20 @@ +import { EventName } from '../../../core/event-name.enum'; +import { getQuickMethodsHandler } from './get-quick-methods.handler'; +import { Message } from '../../../core/message.interface'; +import { PaymentMethod } from '../../../core/payment-method.interface'; + +const mockMessage: Message<{ methods: PaymentMethod[] }> = { + name: EventName.getPaymentQuickMethods, + data: { methods: [] }, +}; +describe('getQuickMethodsHandler', () => { + it('Should handle data', () => { + expect(getQuickMethodsHandler(mockMessage)).toEqual({ + isHandled: true, + value: [], + }); + }); + it('Should return null', () => { + expect(getQuickMethodsHandler({ name: EventName.initPayment })).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/get-quick-methods.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-quick-methods.handler.ts new file mode 100644 index 00000000..367e1092 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-quick-methods.handler.ts @@ -0,0 +1,21 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { PaymentMethod } from '../../../core/payment-method.interface'; +import { Message } from '../../../core/message.interface'; +import { isMethodsEventMessage } from '../../../core/guards/methods-event-message.guard'; +import { EventName } from '../../../core/event-name.enum'; + +export const getQuickMethodsHandler: Handler = ( + message: Message +): { isHandled: boolean; value: PaymentMethod[] } | null => { + if ( + isMethodsEventMessage(message) && + message.name === EventName.getPaymentQuickMethods + ) { + const methods = message.data?.methods; + return { + isHandled: true, + value: methods ?? [], + }; + } + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-regular-methods.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/get-regular-methods.handler.spec.ts new file mode 100644 index 00000000..bc1cf5b7 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-regular-methods.handler.spec.ts @@ -0,0 +1,22 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { PaymentMethod } from '../../../core/payment-method.interface'; +import { getRegularMethodsHandler } from './get-regular-methods.handler'; + +const mockMessage: Message<{ methods: PaymentMethod[] }> = { + name: EventName.getPaymentMethodsList, + data: { methods: [] }, +}; +describe('getRegularMethodsHandler', () => { + it('Should handle data', () => { + expect(getRegularMethodsHandler(mockMessage)).toEqual({ + isHandled: true, + value: [], + }); + }); + it('Should return null', () => { + expect( + getRegularMethodsHandler({ name: EventName.initPayment }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/get-regular-methods.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-regular-methods.handler.ts new file mode 100644 index 00000000..456ed6c0 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-regular-methods.handler.ts @@ -0,0 +1,21 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { PaymentMethod } from '../../../core/payment-method.interface'; +import { Message } from '../../../core/message.interface'; +import { EventName } from '../../../core/event-name.enum'; +import { isMethodsEventMessage } from '../../../core/guards/methods-event-message.guard'; + +export const getRegularMethodsHandler: Handler = ( + message: Message +): { isHandled: boolean; value: PaymentMethod[] } | null => { + if ( + isMethodsEventMessage(message) && + message.name === EventName.getPaymentMethodsList + ) { + const methods = message.data?.methods; + return { + isHandled: true, + value: methods ?? [], + }; + } + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-saved-methods.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/get-saved-methods.handler.spec.ts new file mode 100644 index 00000000..34d1e283 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-saved-methods.handler.spec.ts @@ -0,0 +1,45 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { getSavedMethodsHandler } from './get-saved-methods.handler'; +import { SavedMethod } from '../../../core/saved-method.interface'; + +const mockSavedMethod: SavedMethod = { + currency: 'RUB', + form: { + paymentSid: 'test', + }, + id: 24110725, + isSelected: true, + name: '42761******7082', + pid: 1380, + psName: 'Mastercard', + recurrentType: 'charge', + replaced: false, + type: 'card', + cardExpiryDate: { + month: '12', + year: '2040', + }, + iconName: 'mastercard.svg', + isBabkaPay: false, +}; + +const mockMessage: Message<{ methods: SavedMethod[] }> = { + name: EventName.getSavedMethods, + data: { + methods: [mockSavedMethod], + }, +}; +describe('getSavedMethodsHandler', () => { + it('Should handle data', () => { + expect(getSavedMethodsHandler(mockMessage)).toEqual({ + isHandled: true, + value: [mockSavedMethod], + }); + }); + it('Should return null', () => { + expect( + getSavedMethodsHandler({ name: EventName.getUserBalance }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/get-saved-methods.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-saved-methods.handler.ts new file mode 100644 index 00000000..6f031566 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-saved-methods.handler.ts @@ -0,0 +1,18 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { SavedMethod } from '../../../core/saved-method.interface'; +import { isSavedMethodsEventMessage } from '../../../core/guards/saved-methods-event-message.guard'; + +export const getSavedMethodsHandler: Handler = ( + message: Message +): { isHandled: boolean; value: SavedMethod[] } | null => { + if (!isSavedMethodsEventMessage(message)) { + return null; + } + + const savedMethods = message.data?.methods; + return { + isHandled: true, + value: savedMethods ?? [], + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-user-balance-value.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/get-user-balance-value.handler.spec.ts new file mode 100644 index 00000000..15f32847 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-user-balance-value.handler.spec.ts @@ -0,0 +1,31 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { Balance } from '../../../core/balance.interface'; +import { getUserBalanceValueHandler } from './get-user-balance-value.handler'; + +const mockBalance: Balance = { + amount: 10, + currency: 'USD', +}; + +const mockMessage: Message = { + name: EventName.getUserBalanceValue, + data: { + amount: 10, + currency: 'USD', + }, +}; + +describe('getUserBalanceValueHandler', () => { + it('Should handle data', () => { + expect(getUserBalanceValueHandler(mockMessage)).toEqual({ + isHandled: true, + value: mockBalance, + }); + }); + it('Should return null', () => { + expect( + getUserBalanceValueHandler({ name: EventName.getSavedMethods }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/get-user-balance-value.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-user-balance-value.handler.ts new file mode 100644 index 00000000..074cd5a9 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-user-balance-value.handler.ts @@ -0,0 +1,18 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { Balance } from '../../../core/balance.interface'; +import { isUserBalanceValueEventMessage } from '../../../core/guards/user-balance-value-event-message.guard'; + +export const getUserBalanceValueHandler: Handler = ( + message: Message +): { isHandled: boolean; value: Balance | null | undefined } | null => { + if (!isUserBalanceValueEventMessage(message)) { + return null; + } + + const userBalance = message.data; + return { + isHandled: true, + value: userBalance, + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/get-user-balance.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/get-user-balance.handler.spec.ts new file mode 100644 index 00000000..aa489b25 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-user-balance.handler.spec.ts @@ -0,0 +1,29 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { UserBalance } from '../../../core/user-balance.interface'; +import { getUserBalanceHandler } from './get-user-balance.handler'; + +const mockUserBalance: UserBalance = { + isEnoughUserBalance: true, + isEnoughUserRecurrentBalance: false, +}; + +const mockMessage: Message<{ userBalance: UserBalance }> = { + name: EventName.getUserBalance, + data: { + userBalance: mockUserBalance, + }, +}; +describe('getUserBalanceHandler', () => { + it('Should handle data', () => { + expect(getUserBalanceHandler(mockMessage)).toEqual({ + isHandled: true, + value: mockUserBalance, + }); + }); + it('Should return null', () => { + expect( + getUserBalanceHandler({ name: EventName.getSavedMethods }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/get-user-balance.handler.ts b/src/features/headless-checkout/post-messages-handlers/get-user-balance.handler.ts new file mode 100644 index 00000000..f263ca8e --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/get-user-balance.handler.ts @@ -0,0 +1,18 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { UserBalance } from '../../../core/user-balance.interface'; +import { isUserBalanceEventMessage } from '../../../core/guards/user-balance-event-message.guard'; + +export const getUserBalanceHandler: Handler = ( + message: Message +): { isHandled: boolean; value: UserBalance } | null => { + if (!isUserBalanceEventMessage(message)) { + return null; + } + + const userBalance = message.data!.userBalance; + return { + isHandled: true, + value: userBalance, + }; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/init-form.handler.ts b/src/features/headless-checkout/post-messages-handlers/init-form.handler.ts new file mode 100644 index 00000000..ed18a90a --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/init-form.handler.ts @@ -0,0 +1,20 @@ +import { Form } from '../../../core/form/form.interface'; +import { isInitFormEventMessage } from '../../../core/guards/init-form-event-message.guard'; +import { Message } from '../../../core/message.interface'; +import { Handler } from '../../../core/post-messages-client/handler.type'; + +export const initFormHandler: Handler = ( + message: Message, + callback?: (args?: unknown) => void +): { isHandled: boolean; value?: Form } | null => { + if (isInitFormEventMessage(message)) { + if (typeof callback === 'function') { + callback(message.data); + } + return { + isHandled: true, + value: message.data, + }; + } + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/next-action.handler.ts b/src/features/headless-checkout/post-messages-handlers/next-action.handler.ts new file mode 100644 index 00000000..348f4a22 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/next-action.handler.ts @@ -0,0 +1,17 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { NextAction } from '../../../core/actions/next-action.interface'; +import { isNextActionEventMessage } from '../../../core/guards/next-action-event-message.guard'; +import { Message } from '../../../core/message.interface'; + +export const nextActionHandler: Handler = ( + message: Message +): { isHandled: boolean; value?: NextAction } | null => { + if (isNextActionEventMessage(message)) { + return { + isHandled: true, + value: message.data, + }; + } + + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.spec.ts new file mode 100644 index 00000000..1f077351 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.spec.ts @@ -0,0 +1,24 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { PaymentMethod } from '../../../core/payment-method.interface'; +import { setSecureComponentStylesHandler } from './set-secure-component-styles.handler'; + +const mockMessage: Message<{ methods: PaymentMethod[] }> = { + name: EventName.setSecureComponentStyles, +}; + +describe('setSecureComponentStylesHandler', () => { + it('Should handle data', () => { + expect(setSecureComponentStylesHandler(mockMessage)).toEqual({ + isHandled: true, + }); + }); + + it('Should return null', () => { + expect( + setSecureComponentStylesHandler({ + name: EventName.getPaymentMethodsList, + }), + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.ts b/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.ts new file mode 100644 index 00000000..92ebe1ca --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.ts @@ -0,0 +1,14 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { EventName } from '../../../core/event-name.enum'; + +export const setSecureComponentStylesHandler: Handler = ( + message: Message, +): { isHandled: boolean } | null => { + if (message.name === EventName.setSecureComponentStyles) { + return { + isHandled: true, + }; + } + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/set-token.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/set-token.handler.spec.ts new file mode 100644 index 00000000..bb624075 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/set-token.handler.spec.ts @@ -0,0 +1,19 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { PaymentMethod } from '../../../core/payment-method.interface'; +import { setTokenHandler } from './set-token.handler'; + +const mockMessage: Message<{ methods: PaymentMethod[] }> = { + name: EventName.initPayment, + data: { methods: [] }, +}; +describe('getRegularMethodsHandler', () => { + it('Should handle data', () => { + expect(setTokenHandler(mockMessage)).toEqual({ isHandled: true }); + }); + it('Should return null', () => { + expect( + setTokenHandler({ name: EventName.getPaymentMethodsList }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/set-token.handler.ts b/src/features/headless-checkout/post-messages-handlers/set-token.handler.ts new file mode 100644 index 00000000..4c42143d --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/set-token.handler.ts @@ -0,0 +1,18 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { EventName } from '../../../core/event-name.enum'; + +export const setTokenHandler: Handler = ( + message: Message, + callback?: () => void +): { isHandled: boolean } | null => { + if (message.name === EventName.initPayment) { + if (typeof callback === 'function') { + callback(); + } + return { + isHandled: true, + }; + } + return null; +}; diff --git a/src/features/headless-checkout/post-messages-handlers/update-credit-card-type.handler.ts b/src/features/headless-checkout/post-messages-handlers/update-credit-card-type.handler.ts new file mode 100644 index 00000000..418dd183 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/update-credit-card-type.handler.ts @@ -0,0 +1,16 @@ +import { Message } from '../../../core/message.interface'; +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { isUpdateCreditCardTypeMessage } from '../../../core/guards/update-credit-card-type.guard'; + +export const updateCreditCardTypeHandler: Handler<{ cardType: string }> = ( + message: Message +): { isHandled: boolean; value?: { cardType: string } } | null => { + if (isUpdateCreditCardTypeMessage(message)) { + return { + isHandled: true, + value: message.data, + }; + } + + return null; +}; diff --git a/src/features/headless-checkout/web-components/README.md b/src/features/headless-checkout/web-components/README.md new file mode 100644 index 00000000..31ec1c4f --- /dev/null +++ b/src/features/headless-checkout/web-components/README.md @@ -0,0 +1,83 @@ +# Web Components + +Before creating web components, it is worth familiarizing yourself with the following resource: [https://javascript.info/web-components](https://javascript.info/web-components). + +All components are located in `src/features/headless-checkout/web-components`. + +### Creating a Component + +To create a component, you need to create a directory inside `src/features/headless-checkout/web-components`. + +#### Requirements: + +1. The component should inherit from `WebComponentAbstract`, and secure components should extend from `SecureComponentAbstract`. +2. The tag name for the component should be added to `src/core/web-components/web-component-tag-name.enum.ts`. +3. The component should be added to `src/core/web-components/web-components.map.ts`. This step is necessary for registering the component using `customElements.define()`. + Example: + ```typescript + export const webComponents: { + [key in WebComponentName]: CustomElementConstructor; + } = { + [WebComponentName.CardNumberComponent]: CardNumberComponent, + [WebComponentName.SubmitButtonComponent]: SubmitButtonComponent, + }; + ``` +4. Need to export a component from a file `src/web-components.ts` + +### Using External Services within Web Components + +Since a web component cannot accept constructor arguments, it is advisable to use dependency injection. In this case, you can use the `tsyringe` library. +Here is an example of adding an external service to a web component: + +```typescript +import { singleton } from 'tsyringe'; + +@singleton() // Use the appropriate variant based on the situation +export class PostMessagesClient { + public headlessAppUrl = headlessCheckoutAppUrl; + // Some code... +} + +export class SubmitButtonComponent extends WebComponentAbstract { + private readonly postMessagesClient: PostMessagesClient; + private elementRef!: HTMLButtonElement; + + public constructor() { + super(); + this.postMessagesClient = container.resolve(PostMessagesClient); // Inject the service + } + + protected connectedCallback(): void { + this.postMessagesClient.send({ data: 'message' }); + // Some code... + } + + protected disconnectedCallback(): void { + // Some code... + } +} +``` + +### Usage on the Partner Side + +The partner can use web components after calling the `headlessCheckout.init()` method. + +```typescript +import { + headlessCheckout, + SubmitButtonComponent, +} from '@xsolla/pay-station-sdk'; + +headlessCheckout.init({ + isWebView: false, +}); + +const submitButton = window.document.createElement(new SubmitButtonComponent()); +//some code +``` + +`payment-form.component.html` + +```html + +``` diff --git a/src/features/headless-checkout/web-components/apple-pay/actions-to-stop-waiting.set.ts b/src/features/headless-checkout/web-components/apple-pay/actions-to-stop-waiting.set.ts new file mode 100644 index 00000000..1de631a4 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/actions-to-stop-waiting.set.ts @@ -0,0 +1,7 @@ +import { NextAction } from '../../../../core/actions/next-action.interface'; + +export const actionsToStopWaiting: Set = new Set([ + 'show_fields', + 'status_updated', + 'show_errors', +]); diff --git a/src/features/headless-checkout/web-components/apple-pay/apple-pay-button-classname.const.ts b/src/features/headless-checkout/web-components/apple-pay/apple-pay-button-classname.const.ts new file mode 100644 index 00000000..ac6b0597 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/apple-pay-button-classname.const.ts @@ -0,0 +1 @@ +export const applePayButtonClassName = 'psdk-apple-pay'; diff --git a/src/features/headless-checkout/web-components/apple-pay/apple-pay-commands.enum.ts b/src/features/headless-checkout/web-components/apple-pay/apple-pay-commands.enum.ts new file mode 100644 index 00000000..ffdff355 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/apple-pay-commands.enum.ts @@ -0,0 +1,4 @@ +export enum ApplePayCommands { + applePayReturn = 'apple-pay-return', + applePaySendToken = 'apple-pay-send-token', +} diff --git a/src/features/headless-checkout/web-components/apple-pay/apple-pay-id.const.ts b/src/features/headless-checkout/web-components/apple-pay/apple-pay-id.const.ts new file mode 100644 index 00000000..b9f622b6 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/apple-pay-id.const.ts @@ -0,0 +1 @@ +export const applePayId = 3175; diff --git a/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.scss b/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.scss new file mode 100644 index 00000000..6cd94346 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.scss @@ -0,0 +1,7 @@ +.apple-pay-error { + margin: 0; + padding: 12px; + border-radius: var(--psdk-common-border-radius); + background: var(--psdk-alert-bg); + color: var(--psdk-alert-color); +} diff --git a/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.spec.ts b/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.spec.ts new file mode 100644 index 00000000..3d69c5f3 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.spec.ts @@ -0,0 +1,100 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { ApplePayComponent } from './apple-pay.component'; +import { EventName } from '../../../../core/event-name.enum'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; + +function createComponent(): HTMLElement { + const element = document.createElement(WebComponentTagName.ApplePayComponent); + (document.getElementById('container')! as HTMLElement).appendChild(element); + return element; +} + +describe('ApplePayComponent', () => { + let headlessCheckout: HeadlessCheckout; + let postMessagesClient: PostMessagesClient; + let formSpy: FormSpy; + + window.customElements.define( + WebComponentTagName.ApplePayComponent, + ApplePayComponent + ); + + beforeEach(() => { + document.body.innerHTML = '
'; + + headlessCheckout = { + form: { + onFieldsStatusChange: noopStub, + }, + events: { + onCoreEvent: noopStub, + }, + } as unknown as HeadlessCheckout; + + formSpy = { + listenFormInit: noopStub, + get formWasInit() { + return; + }, + } as unknown as FormSpy; + + postMessagesClient = { + send: noopStub, + } as unknown as PostMessagesClient; + + container.clearInstances(); + + container + .register(FormSpy, { + useValue: formSpy, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }) + .register(Window, { + useValue: window, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Web component should be rendered', () => { + spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); + + createComponent(); + + expect(document.querySelector('iframe')).toBeDefined(); + }); + + it('Should draw error', (done) => { + spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); + // eslint-disable-next-line prefer-const + let element: HTMLElement; + spyOn(headlessCheckout.events, 'onCoreEvent').and.callFake((...args) => { + const eventName = args[0]; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const callback: (value?: unknown) => void = args[2]; + setTimeout(() => { + if (eventName === EventName.applePayError) { + callback({ error: 'paymentError' }); + expect(element.querySelector('.apple-pay-error')).not.toBeNull(); + done(); + } + }); + return noopStub; + }); + + element = createComponent(); + expect(document.querySelector('iframe')).toBeDefined(); + }); +}); diff --git a/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.ts b/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.ts new file mode 100644 index 00000000..3e4a6141 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.ts @@ -0,0 +1,239 @@ +import { SecureComponentAbstract } from '../../../../core/web-components/secure-component/secure-component.abstract'; +import { EventName } from '../../../../core/event-name.enum'; +import { container } from 'tsyringe'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { applePayErrorHandler } from '../../post-messages-handlers/apple-pay/apple-pay-error.handler'; +import { getApplePayComponentTemplate } from './apple-pay.template'; +import { errorsHtmlWrapperClassName } from './errors-html-wrapper-classname.const'; +import { applePayErrors } from './apple-pay.errors.const'; +import i18next from 'i18next'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler'; +import { headlessCheckoutAppUrl } from '../../environment'; +import { openApplePayPageHandler } from '../../post-messages-handlers/apple-pay/open-apple-pay-page.handler'; +import { Message } from '../../../../core/message.interface'; +import { ApplePayCommands } from './apple-pay-commands.enum'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { nextActionHandler } from '../../post-messages-handlers/next-action.handler'; +import { NextAction } from '../../../../core/actions/next-action.interface'; +import { getWaitingProcessingTemplate } from './waiting-processing.template'; +import { waitingProcessingClassname } from './waiting-processing-classname.const'; +import { applePayButtonClassName } from './apple-pay-button-classname.const'; +import { actionsToStopWaiting } from './actions-to-stop-waiting.set'; +import './apple-pay.component.scss'; + +export class ApplePayComponent extends SecureComponentAbstract { + protected componentName: string | null = 'pages/apple-pay'; + private readonly headlessCheckout: HeadlessCheckout; + private readonly postMessagesClient: PostMessagesClient; + private readonly formSpy: FormSpy; + private readonly window: Window; + private readonly listenApplePayWindowCloseDelay = 100; + private applePayWindow?: Window | null; + private listenApplePayWindowCloseTimeout?: ReturnType; + + public constructor() { + super(); + this.headlessCheckout = container.resolve(HeadlessCheckout); + this.postMessagesClient = container.resolve(PostMessagesClient); + this.formSpy = container.resolve(FormSpy); + this.window = container.resolve(Window); + + this.headlessCheckout.events.onCoreEvent( + EventName.applePayError, + applePayErrorHandler, + (res) => { + if (res?.error) { + this.drawError(res.error); + } + }, + ); + + this.headlessCheckout.events.onCoreEvent( + EventName.openApplePayPage, + openApplePayPageHandler, + (res) => { + if (res?.redirectUrl) { + this.openRedirectPage(res.redirectUrl); + } + }, + ); + + this.headlessCheckout.events.onCoreEvent( + EventName.finishLoadComponent, + finishLoadComponentHandler, + (res) => { + if (res?.fieldName && res?.fieldName === this.componentName) { + this.finishLoadingComponentHandler(this.componentName); + } + }, + ); + } + + protected connectedCallback(): void { + this.startLoadingComponentHandler(); + + if (!this.formSpy.formWasInit) { + this.formSpy.listenFormInit(() => this.connectedCallback()); + return; + } + + this.render(); + } + + protected getHtml(): string { + const secureHtml = this.getSecureHtml(); + return getApplePayComponentTemplate(secureHtml); + } + + protected getSecureHtml(): string { + return ``; + } + + private drawError(error: string): void { + const errorMessage = applePayErrors[error]; + if (!errorMessage) { + return; + } + const errorsContainer = this.querySelector( + '.' + errorsHtmlWrapperClassName, + ); + if (!errorsContainer) { + return; + } + const errorElement = document.createElement('p'); + errorElement.classList.add('apple-pay-error'); + errorElement.textContent = i18next.t(errorMessage); + errorsContainer.innerHTML = ''; + errorsContainer.append(errorElement); + } + + private setupWaitingPayment(isWaiting: boolean): void { + if (isWaiting) { + this.drawWaitingElement(); + this.hidePayButton(); + return; + } + this.removeWaitingElement(); + this.showPayButton(); + } + + private hidePayButton(): void { + const applePayButton: HTMLElement | null = this.querySelector( + `.${applePayButtonClassName}`, + ); + if (applePayButton) { + applePayButton.style.visibility = 'hidden'; + applePayButton.style.position = 'absolute'; + } + } + + private showPayButton(): void { + const applePayButton: HTMLElement | null = this.querySelector( + `.${applePayButtonClassName}`, + ); + if (applePayButton) { + applePayButton.style.visibility = 'visible'; + applePayButton.style.position = 'static'; + } + } + + private drawWaitingElement(): void { + const waitingProcessingWrapper = document.createElement('div'); + waitingProcessingWrapper.classList.add(waitingProcessingClassname); + waitingProcessingWrapper.innerHTML = getWaitingProcessingTemplate( + i18next.t('status.processing.title'), + i18next.t('status.processing.description'), + ); + this.append(waitingProcessingWrapper); + } + + private removeWaitingElement(): void { + const waitingProcessingWrapper = this.querySelector( + `.${waitingProcessingClassname}`, + ); + if (waitingProcessingWrapper) { + waitingProcessingWrapper.remove(); + } + } + + private openRedirectPage(redirectUrl: string): void { + this.setupWaitingPayment(true); + this.applePayWindow = this.window.open(redirectUrl); + this.listenApplePayWindowCloseEvent(); + this.window.addEventListener('message', this.handleApplePayWindowMessages); + } + + private listenApplePayWindowCloseEvent(): void { + if (!this.applePayWindow) { + return; + } + + this.listenApplePayWindowCloseTimeout = setTimeout(() => { + if (this.applePayWindow?.closed) { + this.destroyApplePayWindow(); + } else { + this.listenApplePayWindowCloseEvent(); + } + }, this.listenApplePayWindowCloseDelay); + } + + private readonly handleApplePayWindowMessages = ( + event: MessageEvent, + ): void => { + const message = JSON.parse(event.data) as { + command: string; + data: Message['data']; + }; + if (message.command === ApplePayCommands.applePayReturn) { + this.destroyApplePayWindow(); + void this.submitForm(); + return; + } + if (message.command === ApplePayCommands.applePaySendToken) { + this.destroyApplePayWindow(); + void this.submitForm( + (message.data as { applePayToken: string }).applePayToken, + ); + return; + } + }; + + private destroyApplePayWindow(stopLoading?: boolean): void { + this.window.focus(); + if (this.applePayWindow && !this.applePayWindow.closed) { + this.applePayWindow.close(); + } + if (this.listenApplePayWindowCloseTimeout) { + clearTimeout(this.listenApplePayWindowCloseTimeout); + } + this.window.removeEventListener( + 'message', + this.handleApplePayWindowMessages, + ); + + if (stopLoading) { + this.setupWaitingPayment(false); + } + } + + private async submitForm(tokenNonce?: string): Promise { + const msg: Message<{ tokenNonce?: string }> = { + name: EventName.submitApplePayForm, + data: { + tokenNonce, + }, + }; + + return this.postMessagesClient.send(msg, (message) => { + return nextActionHandler(message, (data: unknown) => { + if ((data as NextAction)?.type) { + const nextAction = data as NextAction; + this.setupWaitingPayment(!actionsToStopWaiting.has(nextAction.type)); + } + return true; + }); + }) as Promise; + } +} diff --git a/src/features/headless-checkout/web-components/apple-pay/apple-pay.errors.const.ts b/src/features/headless-checkout/web-components/apple-pay/apple-pay.errors.const.ts new file mode 100644 index 00000000..140ab724 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/apple-pay.errors.const.ts @@ -0,0 +1,4 @@ +export const applePayErrors: { [k: string]: string } = { + paymentError: 'apple-pay.errors.payment', + deviceIsNotCapableError: 'apple-pay.errors.device-is-not-capable', +}; diff --git a/src/features/headless-checkout/web-components/apple-pay/apple-pay.template.ts b/src/features/headless-checkout/web-components/apple-pay/apple-pay.template.ts new file mode 100644 index 00000000..406914de --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/apple-pay.template.ts @@ -0,0 +1,13 @@ +import { errorsHtmlWrapperClassName } from './errors-html-wrapper-classname.const'; +import { applePayButtonClassName } from './apple-pay-button-classname.const'; + +export const getApplePayComponentTemplate = ( + secureHtml: string, + error?: string +): string => { + return ` +
${error ?? ''}
+
+ ${secureHtml} +
`; +}; diff --git a/src/features/headless-checkout/web-components/apple-pay/errors-html-wrapper-classname.const.ts b/src/features/headless-checkout/web-components/apple-pay/errors-html-wrapper-classname.const.ts new file mode 100644 index 00000000..cc223207 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/errors-html-wrapper-classname.const.ts @@ -0,0 +1 @@ +export const errorsHtmlWrapperClassName = 'apple-pay-errors'; diff --git a/src/features/headless-checkout/web-components/apple-pay/waiting-processing-classname.const.ts b/src/features/headless-checkout/web-components/apple-pay/waiting-processing-classname.const.ts new file mode 100644 index 00000000..a48f8fa6 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/waiting-processing-classname.const.ts @@ -0,0 +1 @@ +export const waitingProcessingClassname = 'apple-pay-waiting-processing'; diff --git a/src/features/headless-checkout/web-components/apple-pay/waiting-processing.template.ts b/src/features/headless-checkout/web-components/apple-pay/waiting-processing.template.ts new file mode 100644 index 00000000..b07c1801 --- /dev/null +++ b/src/features/headless-checkout/web-components/apple-pay/waiting-processing.template.ts @@ -0,0 +1,8 @@ +export const getWaitingProcessingTemplate = ( + title: string, + description: string +): string => { + return ` +

${title}

+

${description}

`; +}; diff --git a/src/features/headless-checkout/web-components/base-control/base-control.abstract.ts b/src/features/headless-checkout/web-components/base-control/base-control.abstract.ts new file mode 100644 index 00000000..80d0ba34 --- /dev/null +++ b/src/features/headless-checkout/web-components/base-control/base-control.abstract.ts @@ -0,0 +1,142 @@ +import { container } from 'tsyringe'; +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { EventName } from '../../../../core/event-name.enum'; +import { Message } from '../../../../core/message.interface'; +import { ValidationErrors } from '../../../../core/form/validation-errors.interface'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { ControlComponentConfig } from '../control-component-config.interface'; +import { getControlComponentConfigHandler } from '../get-control-component-config.handler'; +import { + publicControlChangeState, + publicControlOnValueChanges, +} from './element.handlers'; +import { ElementEventName } from './element-events.enum'; + +export abstract class BaseControl< + Config extends ControlComponentConfig, +> extends WebComponentAbstract { + protected postMessagesClient: PostMessagesClient; + protected headlessCheckout: HeadlessCheckout; + protected window: Window; + + protected controlName!: string; + protected config: Config | null = null; + protected isListeningFieldStatusChange = false; + + protected constructor() { + super(); + + this.postMessagesClient = container.resolve(PostMessagesClient); + this.headlessCheckout = container.resolve(HeadlessCheckout); + this.window = container.resolve(Window); + } + + protected async getComponentConfig( + inputName: string, + ): Promise { + const msg: Message<{ inputName: string }> = { + name: EventName.getControlComponentConfig, + data: { + inputName, + }, + }; + + return this.postMessagesClient.send( + msg, + (message) => { + return getControlComponentConfigHandler(message, (controlName) => { + return msg.data?.inputName === controlName; + }); + }, + ) as Promise; + } + + protected notifyOnValueChanges(value: unknown): void { + void this.postMessagesClient.send( + { + name: EventName.publicControlOnValueChanges, + data: { + fieldName: this.controlName, + value, + }, + }, + publicControlOnValueChanges, + ); + } + + protected notifyOnFocusEvent(): void { + void this.postMessagesClient.send( + { + name: EventName.publicControlChangeState, + data: { + fieldName: this.controlName, + event: ElementEventName.focus, + }, + }, + publicControlChangeState, + ); + } + + protected notifyOnBlurEvent(): void { + void this.postMessagesClient.send( + { + name: EventName.publicControlChangeState, + data: { + fieldName: this.controlName, + event: ElementEventName.blur, + }, + }, + publicControlChangeState, + ); + } + + protected listenFieldStatusChange(): void { + if (this.isListeningFieldStatusChange) { + return; + } + + this.isListeningFieldStatusChange = true; + this.headlessCheckout.form.onFieldsStatusChange((fieldsStatus) => { + const fieldStatus = fieldsStatus[this.controlName]; + + if (!this.config || !fieldStatus) { + return; + } + + this.config.error = this.getErrorFromFieldStatus(fieldStatus.errors); + this.updateError(fieldStatus.isFocused); + }); + } + + protected getErrorFromFieldStatus( + errors: ValidationErrors | null, + ): string | null { + if (!errors) { + return null; + } + + const firstErrorKey: string = Object.keys(errors)[0]; + return errors[firstErrorKey]?.message ?? null; + } + + protected updateError(isFieldInFocus: boolean | undefined): void { + const rootElement = this.shadowRoot ?? this; + const errorElement = rootElement.querySelector('.field-error'); + + if (this.config?.error && !isFieldInFocus) { + if (!errorElement) { + const newErrorElement = this.window.document.createElement('div'); + newErrorElement.classList.add('field-error'); + newErrorElement.innerHTML = this.config.error; + rootElement.appendChild(newErrorElement); + } else { + errorElement.innerHTML = this.config.error; + } + } else { + if (errorElement) { + errorElement.remove(); + } + } + } +} diff --git a/src/features/headless-checkout/web-components/base-control/element-events.enum.ts b/src/features/headless-checkout/web-components/base-control/element-events.enum.ts new file mode 100644 index 00000000..dc80e5ef --- /dev/null +++ b/src/features/headless-checkout/web-components/base-control/element-events.enum.ts @@ -0,0 +1,4 @@ +export enum ElementEventName { + blur = 'blur', + focus = 'focus', +} diff --git a/src/features/headless-checkout/web-components/base-control/element.handlers.ts b/src/features/headless-checkout/web-components/base-control/element.handlers.ts new file mode 100644 index 00000000..8a86fd2d --- /dev/null +++ b/src/features/headless-checkout/web-components/base-control/element.handlers.ts @@ -0,0 +1,25 @@ +import { Handler } from '../../../../core/post-messages-client/handler.type'; +import { Message } from '../../../../core/message.interface'; +import { EventName } from '../../../../core/event-name.enum'; + +export const publicControlChangeState: Handler = ( + message: Message, +): { isHandled: boolean } | null => { + if (message.name === EventName.publicControlChangeState) { + return { + isHandled: true, + }; + } + return null; +}; + +export const publicControlOnValueChanges: Handler = ( + message: Message, +): { isHandled: boolean } | null => { + if (message.name === EventName.publicControlOnValueChanges) { + return { + isHandled: true, + }; + } + return null; +}; diff --git a/src/features/headless-checkout/web-components/card-number/card-icons.map.ts b/src/features/headless-checkout/web-components/card-number/card-icons.map.ts new file mode 100644 index 00000000..1aa4016a --- /dev/null +++ b/src/features/headless-checkout/web-components/card-number/card-icons.map.ts @@ -0,0 +1,24 @@ +import { CardType } from './card-type.enum'; +import { cdnIconsUrl } from '../../environment'; + +const iconsPath = `${cdnIconsUrl}/payment-icons`; +const defaultIconPath = `${cdnIconsUrl}/default-payment-icons/default-card.svg`; + +export const cardIconsMap = { + [CardType.AMERICAN_EXPRESS]: `${iconsPath}/american-express.svg`, + [CardType.CHINA_UNION_PAY]: `${iconsPath}/unionpay.svg`, + [CardType.DEFAULT_CARD]: defaultIconPath, + [CardType.DISCOVER]: `${iconsPath}/discover.svg`, + [CardType.HIPERCARD]: `${iconsPath}/hipercard.svg`, + [CardType.MAESTRO]: `${iconsPath}/maestro.svg`, + [CardType.MIR]: `${iconsPath}/mir.svg`, + [CardType.POSTEPAY]: null, + [CardType.AURA]: `${iconsPath}/aura.svg`, + [CardType.DANKORT]: `${iconsPath}/dankort.svg`, + [CardType.DINERS_CLUBS]: `${iconsPath}/dinersclub.svg`, + [CardType.ELO]: `${iconsPath}/elo.svg`, + [CardType.JCB]: `${iconsPath}/jcb.svg`, + [CardType.MASTERCARD]: `${iconsPath}/mastercard.svg`, + [CardType.NARANJA]: null, + [CardType.VISA]: `${iconsPath}/visa.svg`, +}; diff --git a/src/features/headless-checkout/web-components/card-number/card-number-component-attributes.enum.ts b/src/features/headless-checkout/web-components/card-number/card-number-component-attributes.enum.ts new file mode 100644 index 00000000..066b3b86 --- /dev/null +++ b/src/features/headless-checkout/web-components/card-number/card-number-component-attributes.enum.ts @@ -0,0 +1,5 @@ +export enum CardNumberComponentAttributes { + name = 'name', + + icon = 'icon', +} diff --git a/src/features/headless-checkout/web-components/card-number/card-number.component.scss b/src/features/headless-checkout/web-components/card-number/card-number.component.scss new file mode 100644 index 00000000..c29ff891 --- /dev/null +++ b/src/features/headless-checkout/web-components/card-number/card-number.component.scss @@ -0,0 +1,11 @@ +psdk-card-number, psdk-lit-card-number { + .wrapper { + position: relative; + } + + .card-icon { + position: absolute; + top: 12px; + right: 12px; + } +} diff --git a/src/features/headless-checkout/web-components/card-number/card-number.component.ts b/src/features/headless-checkout/web-components/card-number/card-number.component.ts new file mode 100644 index 00000000..f9eff68f --- /dev/null +++ b/src/features/headless-checkout/web-components/card-number/card-number.component.ts @@ -0,0 +1,56 @@ +import { EventName } from '../../../../core/event-name.enum'; +import { updateCreditCardTypeHandler } from '../../post-messages-handlers/update-credit-card-type.handler'; +import { cardIconsMap } from './card-icons.map'; +import { CardType } from './card-type.enum'; +import { CardNumberComponentAttributes } from './card-number-component-attributes.enum'; +import './card-number.component.scss'; +import { property, customElement } from 'lit/decorators.js'; +import { TextComponent } from '../text-component/text.component'; +import { html, TemplateResult } from 'lit'; + +@customElement('psdk-card-number') +export class CardNumberComponent extends TextComponent { + @property({ attribute: false }) + private cardType = 'default'; + + @property({ type: Boolean, attribute: CardNumberComponentAttributes.icon }) + private readonly isCardIconShown = false; + + public connectedCallback(): void { + super.connectedCallback(); + + this.postMessagesClient.listen<{ cardType: string }>( + EventName.updateCreditCardType, + updateCreditCardTypeHandler, + (res) => { + if (this.isCardIconShown && this.cardType !== res?.cardType) { + this.cardType = res?.cardType ? res.cardType : 'default'; + } + }, + ); + } + + protected render(): TemplateResult<1> { + if (!this.formSpy.formWasInit || !this.componentName) { + return html``; + } + + const textComponentTemplate = this.getTextComponentTemplate( + this.getCardIconTemplate(), + ); + + return html`${textComponentTemplate}`; + } + + private getCardIconTemplate(): TemplateResult<1> | null { + const cardIcon = + cardIconsMap[this.cardType as CardType] ?? + cardIconsMap[CardType.DEFAULT_CARD]; + + return this.isCardIconShown + ? html` + + ` + : null; + } +} diff --git a/src/features/headless-checkout/web-components/card-number/card-type.enum.ts b/src/features/headless-checkout/web-components/card-number/card-type.enum.ts new file mode 100644 index 00000000..783d98a8 --- /dev/null +++ b/src/features/headless-checkout/web-components/card-number/card-type.enum.ts @@ -0,0 +1,18 @@ +export enum CardType { + AMERICAN_EXPRESS = 'american-express', + CHINA_UNION_PAY = 'china-union-pay', + DEFAULT_CARD = 'default-card', + DISCOVER = 'discover', + HIPERCARD = 'hipercard', + MAESTRO = 'maestro', + MIR = 'mir', + POSTEPAY = 'postepay', + AURA = 'aura', + DANKORT = 'dankort', + DINERS_CLUBS = 'diners-clubs', + ELO = 'elo', + JCB = 'jcb', + MASTERCARD = 'mastercard', + NARANJA = 'naranja', + VISA = 'visa', +} diff --git a/src/features/headless-checkout/web-components/checkbox/checkbox-attributes.enum.ts b/src/features/headless-checkout/web-components/checkbox/checkbox-attributes.enum.ts new file mode 100644 index 00000000..136a7842 --- /dev/null +++ b/src/features/headless-checkout/web-components/checkbox/checkbox-attributes.enum.ts @@ -0,0 +1,3 @@ +export enum CheckboxAttributes { + name = 'name', +} diff --git a/src/features/headless-checkout/web-components/checkbox/checkbox-component-config.interface.ts b/src/features/headless-checkout/web-components/checkbox/checkbox-component-config.interface.ts new file mode 100644 index 00000000..5ccc2a69 --- /dev/null +++ b/src/features/headless-checkout/web-components/checkbox/checkbox-component-config.interface.ts @@ -0,0 +1,8 @@ +import { XpsBoolean } from '../../../../core/xps-boolean.enum'; +import { ControlComponentConfig } from '../control-component-config.interface'; + +export interface CheckboxComponentConfig extends ControlComponentConfig { + name: string; + initValue: XpsBoolean; + error?: string | null; +} diff --git a/src/features/headless-checkout/web-components/checkbox/checkbox.component.scss b/src/features/headless-checkout/web-components/checkbox/checkbox.component.scss new file mode 100644 index 00000000..6112b95a --- /dev/null +++ b/src/features/headless-checkout/web-components/checkbox/checkbox.component.scss @@ -0,0 +1,68 @@ +@use 'src/styles/mixins/typo.mixin' as typo; + +psdk-checkbox { + $checkbox-size: 18px; + $checkbox-img-size: 14px; + $checkbox-offset: 1px; + @include typo.psdk-typo; + + .checkbox { + position: relative; + display: flex; + + .wrapper { + display: flex; + flex-direction: column; + margin-left: 8px; + } + + &-dummy { + position: absolute; + top: 0; + left: 0; + display: block; + width: $checkbox-size; + height: $checkbox-size; + border-color: var(--psdk-checkbox-unchecked-bg); + border-radius: 2px; + background: var(--psdk-checkbox-unchecked-bg); + + img { + position: absolute; + display: none; + width: $checkbox-img-size; + height: $checkbox-img-size; + inset: $checkbox-offset + 1 $checkbox-offset $checkbox-offset - 1 + $checkbox-offset + 1; + } + } + } + + input { + z-index: 1; + display: inline-block; + width: $checkbox-size; + height: $checkbox-size; + margin: 0; + border: 1px solid transparent; + background: transparent; + cursor: pointer; + appearance: none; + } + + input, + input:checked { + border: 1px solid transparent; + background: transparent; + appearance: none; + } + + input:checked + .checkbox-dummy { + border-color: var(--psdk-checkbox-checked-bg); + background: var(--psdk-checkbox-checked-bg); + + img { + display: block; + } + } +} diff --git a/src/features/headless-checkout/web-components/checkbox/checkbox.component.spec.ts b/src/features/headless-checkout/web-components/checkbox/checkbox.component.spec.ts new file mode 100644 index 00000000..4876e574 --- /dev/null +++ b/src/features/headless-checkout/web-components/checkbox/checkbox.component.spec.ts @@ -0,0 +1,121 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { CheckboxComponent } from './checkbox.component'; +import { CheckboxComponentConfig } from './checkbox-component-config.interface'; +import { XpsBoolean } from '../../../../core/xps-boolean.enum'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { FormLoader } from '../../../../core/form/form-loader'; + +const config: CheckboxComponentConfig = { + name: 'test', + title: 'title', + error: 'error', + initValue: XpsBoolean.false, + placeholder: 'test placeholder', +}; + +function createComponent(): HTMLElement { + const element = document.createElement(WebComponentTagName.CheckboxComponent); + element.setAttribute('name', config.name); + + (document.getElementById('container')! as HTMLElement).appendChild(element); + + return element; +} + +describe('CheckboxComponent', () => { + let headlessCheckout: HeadlessCheckout; + let postMessagesClient: PostMessagesClient; + let windowService: Window; + let formLoader: FormLoader; + + window.customElements.define( + WebComponentTagName.CheckboxComponent, + CheckboxComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
'; + + headlessCheckout = { + form: { onFieldsStatusChange: noopStub }, + } as unknown as HeadlessCheckout; + + postMessagesClient = { + send: noopStub, + } as unknown as PostMessagesClient; + + windowService = window; + + formLoader = { + setupAndAwaitFieldsLoading: noopStub, + setFieldLoaded: noopStub, + } as unknown as FormLoader; + + container.clearInstances(); + + container + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }) + .register(FormSpy, { + useValue: { + get formWasInit() { + return true; + }, + } as FormSpy, + }) + .register(Window, { useValue: windowService }) + .register(FormLoader, { useValue: formLoader }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + spyOn(postMessagesClient, 'send').and.resolveTo(config); + + createComponent(); + expect( + document.querySelector(WebComponentTagName.CheckboxComponent), + ).toBeDefined(); + }); + + it('Should load checkbox component config', (done) => { + const spy = spyOn(postMessagesClient, 'send').and.resolveTo(config); + + createComponent(); + + setTimeout(() => { + expect(spy).toHaveBeenCalled(); + done(); + }, 0); + }); + + it('Should change checkbox value', (done) => { + spyOn(postMessagesClient, 'send').and.resolveTo(config); + + const element = createComponent(); + + const spy = spyOn( + element as unknown as { notifyOnValueChanges(): void }, + 'notifyOnValueChanges', + ); + + setTimeout(() => { + const checkbox = element.querySelector('input'); + checkbox!.checked = true; + checkbox!.dispatchEvent(new Event('change')); + + expect(spy).toHaveBeenCalled(); + done(); + }, 0); + }); +}); diff --git a/src/features/headless-checkout/web-components/checkbox/checkbox.component.ts b/src/features/headless-checkout/web-components/checkbox/checkbox.component.ts new file mode 100644 index 00000000..bc900b7c --- /dev/null +++ b/src/features/headless-checkout/web-components/checkbox/checkbox.component.ts @@ -0,0 +1,79 @@ +import { BaseControl } from '../base-control/base-control.abstract'; +import { CheckboxAttributes } from './checkbox-attributes.enum'; +import { getCheckboxComponentTemplate } from './checkbox.template'; +import { XpsBoolean } from '../../../../core/xps-boolean.enum'; +import { CheckboxComponentConfig } from './checkbox-component-config.interface'; +import './checkbox.component.scss'; +import { container } from 'tsyringe'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; + +export class CheckboxComponent extends BaseControl { + protected config: CheckboxComponentConfig | null = null; + protected readonly formSpy: FormSpy; + + public constructor() { + super(); + this.formSpy = container.resolve(FormSpy); + } + + public get nameAttr(): string { + return this.getAttribute(CheckboxAttributes.name) ?? ''; + } + + public static get observedAttributes(): string[] { + return [CheckboxAttributes.name]; + } + + protected connectedCallback(): void { + this.controlName = this.nameAttr; + + if (!this.controlName) { + return; + } + + if (!this.formSpy.formWasInit) { + this.formSpy.listenFormInit(() => this.connectedCallback()); + return; + } + + this.listenFieldStatusChange(); + + void this.getComponentConfig(this.controlName).then((config) => { + this.config = config as CheckboxComponentConfig; + this.render(); + this.addEventListenerToElement(this.inputRef, 'change', (event: Event) => + this.notifyOnValueChanges(event), + ); + this.finishLoadingFormControlHandler(this.controlName); + }); + } + + protected attributeChangedCallback(): void { + if (!this.controlName) { + return this.connectedCallback(); + } + + if (!this.formSpy.formWasInit) { + this.formSpy.listenFormInit(() => super.attributeChangedCallback()); + } + } + + protected getHtml(): string { + if (!this.config) { + return ''; + } + + return getCheckboxComponentTemplate(this.config); + } + + private get inputRef(): HTMLElement { + return this.querySelector('input') as HTMLElement; + } + + protected notifyOnValueChanges(event: Event): void { + const isChecked = (event.target as HTMLInputElement).checked; + const value = isChecked ? XpsBoolean.true : XpsBoolean.false; + + super.notifyOnValueChanges(value); + } +} diff --git a/src/features/headless-checkout/web-components/checkbox/checkbox.template.spec.ts b/src/features/headless-checkout/web-components/checkbox/checkbox.template.spec.ts new file mode 100644 index 00000000..3906c39a --- /dev/null +++ b/src/features/headless-checkout/web-components/checkbox/checkbox.template.spec.ts @@ -0,0 +1,21 @@ +import { getCheckboxComponentTemplate } from './checkbox.template'; +import { CheckboxComponentConfig } from './checkbox-component-config.interface'; +import { XpsBoolean } from '../../../../core/xps-boolean.enum'; + +const mockedData: CheckboxComponentConfig = { + name: 'name', + title: 'title', + error: 'error', + initValue: XpsBoolean.true, + placeholder: 'test placeholder', +}; + +describe('getCheckboxComponentTemplate', () => { + it('Should draw checkbox template', () => { + const template = getCheckboxComponentTemplate(mockedData); + + expect(template).toContain('name'); + expect(template).toContain('checked'); + expect(template).toContain('test placeholder'); + }); +}); diff --git a/src/features/headless-checkout/web-components/checkbox/checkbox.template.ts b/src/features/headless-checkout/web-components/checkbox/checkbox.template.ts new file mode 100644 index 00000000..3fd454f0 --- /dev/null +++ b/src/features/headless-checkout/web-components/checkbox/checkbox.template.ts @@ -0,0 +1,31 @@ +import { XpsBoolean } from '../../../../core/xps-boolean.enum'; +import { CheckboxComponentConfig } from './checkbox-component-config.interface'; +import { cdnIconsUrl } from '../../environment'; + +const check = `${cdnIconsUrl}/check.svg`; + +export const getCheckboxComponentTemplate = ( + config: CheckboxComponentConfig, +): string => { + const isChecked = config.initValue === XpsBoolean.true; + const placeholder = config.placeholder; + const error = config.error; + + return ` +
+ +
+ ${isChecked ? `checkbox` : ''} +
+
+ ${placeholder ? `${placeholder}` : ''} + ${error ? `${error}` : ''} +
+
+ `; +}; diff --git a/src/features/headless-checkout/web-components/control-component-config.interface.ts b/src/features/headless-checkout/web-components/control-component-config.interface.ts new file mode 100644 index 00000000..afd52a4d --- /dev/null +++ b/src/features/headless-checkout/web-components/control-component-config.interface.ts @@ -0,0 +1,8 @@ +export interface ControlComponentConfig { + controlType?: string; + name?: string; + dataType?: string; + title?: string; + placeholder?: string; + error?: string | null; +} diff --git a/src/features/headless-checkout/web-components/finance-details/cart-items.template.spec.ts b/src/features/headless-checkout/web-components/finance-details/cart-items.template.spec.ts new file mode 100644 index 00000000..80edc477 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/cart-items.template.spec.ts @@ -0,0 +1,85 @@ +import i18next from 'i18next'; +import { CartItem } from '../../../../core/finance-details/cart-item.interface'; +import { getCartItemsTemplate } from './cart-items.template'; + +const item: CartItem = { + key: 'key', + hasDefaultImg: false, + title: 'title', + price: { + amount: 10, + currency: 'USD', + }, + description: 'description', + quantity: 1, + isBonus: false, +}; + +const itemWithImage = { + ...item, + imgSrc: 'imgSrc', +}; + +const itemWithDiscount: CartItem = { + ...item, + priceBeforeDiscount: { + amount: 1, + currency: 'USD', + }, +}; + +const itemWithTax: CartItem = { + ...item, + tax: { + key: 'vat', + title: 'tax-title', + money: { + amount: 1, + currency: 'USD', + }, + }, +}; + +const itemWithQuantity: CartItem = { + ...item, + quantity: 2, +}; + +const itemCheckout: CartItem = { + ...item, + key: 'checkout', + title: '', +}; + +describe('getCartItemsTemplate', () => { + it('Should draw empty items', () => { + expect(getCartItemsTemplate([])).toEqual(''); + }); + + it('Should not draw image', () => { + expect(getCartItemsTemplate([item])).not.toContain('img'); + }); + + it('Should draw image', () => { + expect(getCartItemsTemplate([itemWithImage])).toContain('img'); + }); + + it('Should draw price before discount', () => { + expect(getCartItemsTemplate([itemWithDiscount])).toContain( + 'price-before-discount' + ); + }); + + it('Should draw tax', () => { + expect(getCartItemsTemplate([itemWithTax])).toContain('tax'); + }); + + it('Should draw item quantity', () => { + expect(getCartItemsTemplate([itemWithQuantity])).toContain('2 x'); + }); + + it('Should draw checkout item', () => { + spyOn(i18next, 't').and.returnValue('In-game purchase'); + expect(getCartItemsTemplate([itemCheckout])).toContain('In-game purchase'); + }); +}); diff --git a/src/features/headless-checkout/web-components/finance-details/cart-items.template.ts b/src/features/headless-checkout/web-components/finance-details/cart-items.template.ts new file mode 100644 index 00000000..3377bc6a --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/cart-items.template.ts @@ -0,0 +1,104 @@ +import i18next from 'i18next'; +import { CartItem } from '../../../../core/finance-details/cart-item.interface'; +import { getPriceTextTemplate } from './price-text.template'; +import { taxTranslationMap } from './tax-translation-map.const'; + +const cartItemTitleTranslationMap = new Map([ + ['checkout', 'finance-details-cart-items-checkout-title'], +]); + +const cartItemTaxTranslationMap = new Map(taxTranslationMap); + +function translateCartItems(items: CartItem[] = []): CartItem[] { + return items.map((item) => { + const title = + !item.title && item.key && cartItemTitleTranslationMap.has(item.key) + ? i18next.t(cartItemTitleTranslationMap.get(item.key)!) + : item.title; + + const content = + item.tax?.key && cartItemTaxTranslationMap.has(item.tax.key) + ? i18next.t(cartItemTaxTranslationMap.get(item.tax.key)!, { + percent: item.tax.rate, + }) + : item.tax?.content; + + return { + ...item, + title: title ?? item.title, + tax: { + ...item.tax, + content, + }, + }; + }); +} + +function getCartItemImage(item: CartItem): string { + if (!item.imgSrc) { + return ''; + } + + return ` +
+ ${item.title} +
+ `; +} + +function getCartItemText(item: CartItem): string { + const title = + item.quantity && item.quantity > 1 + ? `${item.quantity} x ${item.title}` + : item.title; + const description = item.description?.replace(/\\n/g, '
'); + return ` +
+
+
${title ?? ''}
+
${description ?? ''}
+ ${item.tax ? getPriceTextTemplate(item.tax, null, 'tax') : ''} +
+
+ ${getPriceTextTemplate(null, item.price, 'price')} + ${ + item.priceBeforeDiscount?.amount + ? getPriceTextTemplate( + null, + item.priceBeforeDiscount, + 'price-before-discount', + ) + : '' + } +
+
+ `; +} + +export const getCartItemsTemplate = (cartItems: CartItem[] = []): string => { + if (!cartItems?.length) { + return ''; + } + + const items = translateCartItems(cartItems); + const itemLines = items.map((item) => { + return ` +
+ ${getCartItemImage(item)} + ${getCartItemText(item)} +
+ `; + }); + + return ` +
+ ${itemLines.join('')} +
+ `; +}; diff --git a/src/features/headless-checkout/web-components/finance-details/croatian-exchange-rate.template.spec.ts b/src/features/headless-checkout/web-components/finance-details/croatian-exchange-rate.template.spec.ts new file mode 100644 index 00000000..538e5afc --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/croatian-exchange-rate.template.spec.ts @@ -0,0 +1,39 @@ +import { CountryCode } from '../../../../core/country/country-code.enum'; +import { FinanceDetails } from '../../../../core/finance-details/finance-details.interface'; +import { getCroatianExchangeRateTemplate } from './croatian-exchange-rate.template'; +import { Currency } from '../../../../core/currency/currency.enum'; + +const financeDetails = { + paymentCountry: CountryCode.India, + cartSummary: { + total: { + money: { + currency: Currency.USD, + }, + }, + }, +} as unknown as FinanceDetails; + +const financeDetailsCroatia = { + paymentCountry: CountryCode.Croatia, + cartSummary: { + total: { + money: { + amount: 1, + currency: Currency.EUR, + }, + }, + }, +} as unknown as FinanceDetails; + +describe('getCroatianExchangeRateTemplate', () => { + it('Should not draw', () => { + expect(getCroatianExchangeRateTemplate(financeDetails)).toEqual(''); + }); + + it('Should draw for Croatia country and EUR currency', () => { + const template = getCroatianExchangeRateTemplate(financeDetailsCroatia); + expect(template).toContain('equal-value'); + expect(template).toContain('details'); + }); +}); diff --git a/src/features/headless-checkout/web-components/finance-details/croatian-exchange-rate.template.ts b/src/features/headless-checkout/web-components/finance-details/croatian-exchange-rate.template.ts new file mode 100644 index 00000000..9e31e807 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/croatian-exchange-rate.template.ts @@ -0,0 +1,39 @@ +import i18next from 'i18next'; +import { CountryCode } from '../../../../core/country/country-code.enum'; +import { Currency } from '../../../../core/currency/currency.enum'; +import { FinanceDetails } from '../../../../core/finance-details/finance-details.interface'; + +export const getCroatianExchangeRateTemplate = ( + financeDetails: FinanceDetails +): string => { + const cartSummary = financeDetails.cartSummary; + const isCroatia = financeDetails.paymentCountry === CountryCode.Croatia; + const isEuroCurrency = cartSummary.total.money?.currency === Currency.EUR; + const croatianCurrencyExchangeRate = 7.5345; + + if (!isCroatia || !isEuroCurrency) { + return ''; + } + + function getCroatianCurrencyRate(): number { + const amount = cartSummary.total.money?.amount + ? cartSummary.total.money?.amount + : 0; + const ceilDecimals = 100; + + return ( + Math.ceil(croatianCurrencyExchangeRate * amount * ceilDecimals) / + ceilDecimals + ); + } + + return ` +
+
${i18next.t('finance-details-hrk-equal', { + value: getCroatianCurrencyRate(), + })}
+
${i18next.t( + 'finance-details-hrk-exchange-rate' + )}
+
`; +}; diff --git a/src/features/headless-checkout/web-components/finance-details/finance-details.component.scss b/src/features/headless-checkout/web-components/finance-details/finance-details.component.scss new file mode 100644 index 00000000..19fdbaaf --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/finance-details.component.scss @@ -0,0 +1,130 @@ +@use 'src/styles/mixins/typo.mixin' as typo; +@use 'src/styles/mixins/payment-methods.mixin' as payment-methods; +@use 'src/styles/mixins/cart.mixin' as cart; + +psdk-finance-details { + @include typo.psdk-typo; + + & > * { + margin-bottom: 12px; + } + + & > *:last-child { + margin-bottom: 0; + } + + .subtotal-row, + .total-row, + .details, + .total-details-row, + .transaction-invoice { + display: flex; + justify-content: space-between; + } + + .details { + flex-wrap: wrap; + } + + .title { + margin-right: 8px; + font-weight: 500; + } + + .price-container { + display: flex; + gap: 4px; + } + + .one-item { + .cart-item { + display: flex; + flex-direction: column; + align-items: center; + } + + .image-container { + overflow: hidden; + width: 100px; + height: 100px; + margin-bottom: 12px; + border-radius: 16px; + + & img { + width: 100%; + height: auto; + } + } + + .details-wrapper { + display: flex; + width: 100%; + } + + .details { + display: flex; + flex-grow: 1; + flex-direction: column; + } + } + + .many-items { + .cart-item { + display: flex; + min-height: 54px; + margin-bottom: 12px; + } + + .image-container { + flex-shrink: 0; + overflow: hidden; + width: 48px; + height: 48px; + margin-right: 12px; + border-radius: 12px; + + & img { + width: 100%; + height: auto; + } + } + + .details-wrapper { + display: flex; + flex-grow: 1; + } + + .details { + display: flex; + flex-grow: 1; + flex-direction: column; + } + } + + .price-wrapper { + flex-shrink: 0; + margin-left: 8px; + } + + .price-before-discount { + @include cart.line-through; + + color: var(--psdk-secondary-color); + } + + .tax { + display: flex; + gap: 4px; + color: var(--psdk-secondary-color); + } + + .description { + flex-grow: 1; + word-break: break-all; + } + + .divider { + border: none; + border-bottom: 1px solid var(--psdk-divider-color); + } +} diff --git a/src/features/headless-checkout/web-components/finance-details/finance-details.component.spec.ts b/src/features/headless-checkout/web-components/finance-details/finance-details.component.spec.ts new file mode 100644 index 00000000..bd2a0975 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/finance-details.component.spec.ts @@ -0,0 +1,129 @@ +import { container } from 'tsyringe'; +import { Currency } from '../../../../core/currency/currency.enum'; +import { FinanceDetails } from '../../../../core/finance-details/finance-details.interface'; +import { Handler } from '../../../../core/post-messages-client/handler.type'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { FinanceDetailsComponent } from './finance-details.component'; + +class HeadlessCheckoutMock { + public events = { + onCoreEvent: ( + name: string, + handler: Handler, + callback: (value: FinanceDetails | null) => void + ) => { + this.addEventListener(callback); + }, + }; + private readonly listenerts: Array<(value: FinanceDetails | null) => void> = + []; + + public addEventListener( + callback: (value: FinanceDetails | null) => void + ): void { + this.listenerts.push(callback); + } + + public emitEvent(value: FinanceDetails | null): void { + this.listenerts.forEach((callback) => callback(value)); + } +} + +const financeDetails = { + finance: {}, + cartItems: [], + cartSummary: { + total: { + money: { + amount: 1, + currency: Currency.USD, + }, + }, + }, +} as unknown as FinanceDetails; + +function createComponent(): void { + const element = document.createElement( + WebComponentTagName.FinanceDetailsComponent + ); + + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +describe('FinanceDetailsComponent', () => { + let headlessCheckout: HeadlessCheckoutMock; + let headlessCheckoutSpy: HeadlessCheckoutSpy; + + window.customElements.define( + WebComponentTagName.FinanceDetailsComponent, + FinanceDetailsComponent + ); + + beforeEach(() => { + document.body.innerHTML = '
'; + + headlessCheckout = new HeadlessCheckoutMock(); + headlessCheckoutSpy = { + listenAppInit: noopStub, + get appWasInit() { + return; + }, + } as unknown as HeadlessCheckoutSpy; + + container.clearInstances(); + + container + .register(HeadlessCheckoutSpy, { + useValue: headlessCheckoutSpy, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout as unknown as HeadlessCheckout, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.FinanceDetailsComponent) + ).not.toBeNull(); + }); + + it('Should subscribe to finance details event', () => { + const spy = spyOn(headlessCheckout.events, 'onCoreEvent'); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should not draw for empty finance details event', () => { + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + headlessCheckout.emitEvent(null); + const element = document.querySelector( + WebComponentTagName.FinanceDetailsComponent + ); + expect(element!.innerHTML).toEqual(''); + }); + + it('Should draw on finance details event', () => { + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + headlessCheckout.emitEvent(financeDetails as unknown as FinanceDetails); + expect( + document.querySelector(WebComponentTagName.FinanceDetailsComponent) + ).not.toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/web-components/finance-details/finance-details.component.ts b/src/features/headless-checkout/web-components/finance-details/finance-details.component.ts new file mode 100644 index 00000000..39fbe1c5 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/finance-details.component.ts @@ -0,0 +1,49 @@ +import { EventName } from '../../../../core/event-name.enum'; +import { container } from 'tsyringe'; +import { FinanceDetails } from '../../../../core/finance-details/finance-details.interface'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { getFinanceDetailsHandler } from '../../post-messages-handlers/get-finance-details.handler'; +import { getFinanceDetailsTemplate } from './finance-details.template'; +import './finance-details.component.scss'; + +export class FinanceDetailsComponent extends WebComponentAbstract { + private readonly headlessCheckout: HeadlessCheckout; + private readonly headlessCheckoutSpy: HeadlessCheckoutSpy; + private financeDetails?: FinanceDetails | null = null; + + public constructor() { + super(); + this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy); + this.headlessCheckout = container.resolve(HeadlessCheckout); + } + + protected connectedCallback(): void { + if (!this.headlessCheckoutSpy.appWasInit) { + this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); + return; + } + void this.headlessCheckout.events.onCoreEvent( + EventName.financeDetails, + getFinanceDetailsHandler, + (financeDetails) => this.financeDetailsLoadedHandler(financeDetails), + ); + } + + protected getHtml(): string { + if (this.financeDetails) { + return getFinanceDetailsTemplate(this.financeDetails); + } + + return ''; + } + + private readonly financeDetailsLoadedHandler = ( + financeDetails?: FinanceDetails | null, + ): void => { + this.financeDetails = financeDetails; + + this.render(); + }; +} diff --git a/src/features/headless-checkout/web-components/finance-details/finance-details.template.ts b/src/features/headless-checkout/web-components/finance-details/finance-details.template.ts new file mode 100644 index 00000000..0115f748 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/finance-details.template.ts @@ -0,0 +1,26 @@ +import { FinanceDetails } from '../../../../core/finance-details/finance-details.interface'; +import { getCartItemsTemplate } from './cart-items.template'; +import { getCroatianExchangeRateTemplate } from './croatian-exchange-rate.template'; +import { getShippingTemplate } from './shipping.template'; +import { getSubtotalDetailsTemplate } from './subtotal-details.template'; +import { getTransactionDetailsTemplate } from './transaction-details.template'; +import { getTotalDetailsTemplate } from './total-details.template'; + +export const getFinanceDetailsTemplate = ( + financeDetails: FinanceDetails, +): string => { + return [ + getTransactionDetailsTemplate( + financeDetails.cartSummary?.transactionDetails, + ), + getCartItemsTemplate(financeDetails.cartItems), + financeDetails.cartItems && '
', + getShippingTemplate(financeDetails.cartSummary), + getSubtotalDetailsTemplate( + financeDetails.finance, + financeDetails.cartSummary, + ), + getCroatianExchangeRateTemplate(financeDetails), + getTotalDetailsTemplate(financeDetails.finance, financeDetails.cartSummary), + ].join(''); +}; diff --git a/src/features/headless-checkout/web-components/finance-details/price-text.template.ts b/src/features/headless-checkout/web-components/finance-details/price-text.template.ts new file mode 100644 index 00000000..0b513a16 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/price-text.template.ts @@ -0,0 +1,25 @@ +import { Price } from '../../../../core/finance-details/price.interface'; +import { CartLine } from '../../../../core/finance-details/cart-line.interface'; + +export const getPriceTextTemplate = ( + cartLine?: CartLine | null, + price?: Price | null, + className?: string +): string => { + const priceLineContent = cartLine?.content ?? ''; + const priceLineAmount = cartLine?.money?.amount ?? ''; + const priceLineCurrency = cartLine?.money?.currency ?? ''; + const amount = price?.amount ?? ''; + const currency = price?.currency ?? ''; + + return ` + + `; +}; diff --git a/src/features/headless-checkout/web-components/finance-details/price-text/price-text-attributes.enum.ts b/src/features/headless-checkout/web-components/finance-details/price-text/price-text-attributes.enum.ts new file mode 100644 index 00000000..81d042b0 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/price-text/price-text-attributes.enum.ts @@ -0,0 +1,7 @@ +export enum PriceTextAttributes { + priceLineContent = 'price-line-content', + priceLineAmount = 'price-line-amount', + priceLineCurrency = 'price-line-currency', + amount = 'amount', + currency = 'currency', +} diff --git a/src/features/headless-checkout/web-components/finance-details/price-text/price-text.component.spec.ts b/src/features/headless-checkout/web-components/finance-details/price-text/price-text.component.spec.ts new file mode 100644 index 00000000..98ef5c6d --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/price-text/price-text.component.spec.ts @@ -0,0 +1,52 @@ +import { container } from 'tsyringe'; +import { CurrencyPipe } from '../../../../../core/pipes/currency/currency.pipe'; +import { WebComponentTagName } from '../../../../../core/web-components/web-component-tag-name.enum'; +import { PriceTextComponent } from './price-text.component'; + +function createComponent(): void { + const element = document.createElement( + WebComponentTagName.PriceTextComponent, + ); + + element.setAttribute('price-line-content', 'content'); + element.setAttribute('price-line-amount', '1'); + element.setAttribute('price-line-currency', 'USD'); + element.setAttribute('amount', '10'); + element.setAttribute('currency', 'USD'); + + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +describe('PriceTextComponent', () => { + window.customElements.define( + WebComponentTagName.PriceTextComponent, + PriceTextComponent, + ); + + const currencyPipe = { transform: () => '' } as unknown as CurrencyPipe; + + beforeEach(() => { + document.body.innerHTML = '
'; + + container.clearInstances(); + + container.register(CurrencyPipe, { useValue: currencyPipe }); + spyOn(container, 'resolve').and.returnValue(currencyPipe); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should draw price content', () => { + createComponent(); + expect(document.querySelector('.content')!.textContent).toContain( + 'content', + ); + }); + + it('Should draw price ', () => { + createComponent(); + expect(document.querySelector('.price')).not.toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/web-components/finance-details/price-text/price-text.component.ts b/src/features/headless-checkout/web-components/finance-details/price-text/price-text.component.ts new file mode 100644 index 00000000..faee4376 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/price-text/price-text.component.ts @@ -0,0 +1,60 @@ +import { WebComponentAbstract } from '../../../../../core/web-components/web-component.abstract'; +import { PriceTextAttributes } from './price-text-attributes.enum'; +import { getPriceTextTemplate } from './price-text.template'; + +export class PriceTextComponent extends WebComponentAbstract { + private priceLineContent: string | null = null; + private priceLineAmount: number | null = null; + private priceLineCurrency: string | null = null; + private amount: number | null = null; + private currency: string | null = null; + + public static get observedAttributes(): string[] { + return [ + PriceTextAttributes.priceLineContent, + PriceTextAttributes.priceLineAmount, + PriceTextAttributes.priceLineCurrency, + PriceTextAttributes.amount, + PriceTextAttributes.currency, + ]; + } + + protected connectedCallback(): void { + this.readAttributes(); + this.render(); + } + + protected getHtml(): string { + const priceLine = + this.priceLineAmount === null + ? null + : { + amount: this.priceLineAmount, + currency: this.priceLineCurrency ?? '', + }; + + const price = + this.amount === null + ? null + : { + amount: this.amount, + currency: this.currency ?? '', + }; + + return getPriceTextTemplate(this.priceLineContent, priceLine, price); + } + + private readAttributes(): void { + this.priceLineContent = this.getAttribute( + PriceTextAttributes.priceLineContent, + ); + this.priceLineAmount = this.getNumberAttribute( + PriceTextAttributes.priceLineAmount, + ); + this.priceLineCurrency = this.getAttribute( + PriceTextAttributes.priceLineCurrency, + ); + this.amount = this.getNumberAttribute(PriceTextAttributes.amount); + this.currency = this.getAttribute(PriceTextAttributes.currency); + } +} diff --git a/src/features/headless-checkout/web-components/finance-details/price-text/price-text.template.ts b/src/features/headless-checkout/web-components/finance-details/price-text/price-text.template.ts new file mode 100644 index 00000000..a53db61a --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/price-text/price-text.template.ts @@ -0,0 +1,34 @@ +import { CurrencyPipe } from '../../../../../core/pipes/currency/currency.pipe'; +import { Price } from '../../../../../core/finance-details/price.interface'; +import { container } from 'tsyringe'; + +export const getPriceTextTemplate = ( + content: string | null, + priceLine: Price | null, + price: Price | null, +): string => { + const lines = []; + const currencyPipe = container.resolve(CurrencyPipe); + + if (content) { + lines.push(`
${content}
`); + } + + if (priceLine) { + lines.push( + `
${ + currencyPipe.transform(priceLine.amount, priceLine.currency) ?? '' + }
`, + ); + } + + if (price) { + lines.push( + `
${ + currencyPipe.transform(price.amount, price.currency) ?? '' + }
`, + ); + } + + return lines.join(''); +}; diff --git a/src/features/headless-checkout/web-components/finance-details/shipping.template.spec.ts b/src/features/headless-checkout/web-components/finance-details/shipping.template.spec.ts new file mode 100644 index 00000000..68702457 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/shipping.template.spec.ts @@ -0,0 +1,22 @@ +import { CartSummary } from '../../../../core/finance-details/cart-summary.interface'; +import { getShippingTemplate } from './shipping.template'; + +const cartSummary = {} as unknown as CartSummary; +const cartSummaryWithShipping = { + shipping: [ + { + title: 'title', + }, + ], +} as unknown as CartSummary; + +describe('getShippingTemplate', () => { + it('Should not draw for empty shipping', () => { + expect(getShippingTemplate(cartSummary)).toEqual(''); + }); + + it('Should draw for Croatia country and EUR currency', () => { + const template = getShippingTemplate(cartSummaryWithShipping); + expect(template).toContain('shipping-row'); + }); +}); diff --git a/src/features/headless-checkout/web-components/finance-details/shipping.template.ts b/src/features/headless-checkout/web-components/finance-details/shipping.template.ts new file mode 100644 index 00000000..5aaca386 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/shipping.template.ts @@ -0,0 +1,24 @@ +import { CartSummary } from '../../../../core/finance-details/cart-summary.interface'; +import { getPriceTextTemplate } from './price-text.template'; + +export const getShippingTemplate = (cartSummary: CartSummary): string => { + const shipping = cartSummary.shipping; + + if (!shipping?.length) { + return ''; + } + + const lines = shipping.map((item) => { + return ` +
+
${item.title ?? ''}
+ ${getPriceTextTemplate(item)} +
+ `; + }); + + return ` +
+ ${lines.join('')} +
`; +}; diff --git a/src/features/headless-checkout/web-components/finance-details/subtotal-details.templat.spec.ts b/src/features/headless-checkout/web-components/finance-details/subtotal-details.templat.spec.ts new file mode 100644 index 00000000..60b3d19a --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/subtotal-details.templat.spec.ts @@ -0,0 +1,67 @@ +import i18next from 'i18next'; +import { Currency } from '../../../../core/currency/currency.enum'; +import { CartSummary } from '../../../../core/finance-details/cart-summary.interface'; +import { XpsFinance } from '../../../../core/finance-details/xps-finance.interface'; +import { getSubtotalDetailsTemplate } from './subtotal-details.template'; + +const finance = { + vat_user: { + percent: 10, + }, +} as unknown as XpsFinance; +const summary = { + subtotalDetails: [ + { + title: 'title', + money: { + amount: 1, + currency: Currency.USD, + }, + }, + ], +} as unknown as CartSummary; +const summaryWithVatItem = { + subtotalDetails: [ + { + key: 'vat', + title: 'vat', + money: { + amount: 1, + currency: Currency.USD, + }, + }, + ], +} as unknown as CartSummary; +const summaryWithConvertion = { + ...summary, + subtotal: { + money: { + amount: 1, + currency: Currency.USD, + }, + }, + subtotalPayment: { + money: { + amount: 1, + currency: Currency.EUR, + }, + }, +} as unknown as CartSummary; + +describe('getSubtotalDetailsTemplate', () => { + it('Should draw title', () => { + const template = getSubtotalDetailsTemplate(finance, summaryWithConvertion); + expect(template).toContain('title'); + }); + + it('Should draw converted price', () => { + const template = getSubtotalDetailsTemplate(finance, summaryWithConvertion); + expect(template).toContain('converted-price'); + }); + + it('Should draw vat', () => { + spyOn(i18next, 't').and.returnValue('vat'); + const template = getSubtotalDetailsTemplate(finance, summaryWithVatItem); + expect(template).toContain('vat'); + }); +}); diff --git a/src/features/headless-checkout/web-components/finance-details/subtotal-details.template.ts b/src/features/headless-checkout/web-components/finance-details/subtotal-details.template.ts new file mode 100644 index 00000000..a87b4fc9 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/subtotal-details.template.ts @@ -0,0 +1,101 @@ +import i18next from 'i18next'; +import { CartLine } from '../../../../core/finance-details/cart-line.interface'; +import { CartSummary } from '../../../../core/finance-details/cart-summary.interface'; +import { XpsFinance } from '../../../../core/finance-details/xps-finance.interface'; +import { getPriceTextTemplate } from './price-text.template'; +import { taxTranslationMap } from './tax-translation-map.const'; + +const translationMap = new Map([ + ...taxTranslationMap, + ['fee', 'finance-details-subtotal-details-fee'], + ['discount', 'finance-details-subtotal-details-discount'], + ['user-balance', 'finance-details-subtotal-details-user-balance'], +]); + +function translateSubtotalDetails( + finance: XpsFinance, + items: CartLine[] = [], +): CartLine[] { + return items.map((item) => { + if (!item.key || !translationMap.has(item.key)) { + return item; + } + + return { + ...item, + title: i18next.t(translationMap.get(item.key)!, { + percent: finance?.vat_user?.percent, + }), + } as CartLine; + }); +} + +function getSubtotalTitle(cartSummary: CartSummary): string { + const lines = []; + + if (cartSummary.subtotal) { + lines.push( + `
${i18next.t('finance-details-subtotal-title')}
`, + ); + } + + if (cartSummary.subtotal && cartSummary.subtotalPayment) { + const hasConversion = + cartSummary.subtotal.money?.currency !== + cartSummary.subtotalPayment.money?.currency; + + const conversionPrice = hasConversion + ? `${getPriceTextTemplate( + cartSummary.subtotal, + null, + 'price converted-price', + )} =` + : ''; + + lines.push(` +
+ ${conversionPrice} + ${getPriceTextTemplate(cartSummary.subtotalPayment, null, 'price')} +
+ `); + } + + return lines?.length + ? ` +
+ ${lines.join('')} +
` + : ''; +} + +function getSubtotalContent( + finance: XpsFinance, + cartSummary: CartSummary, +): string { + const lines = translateSubtotalDetails( + finance, + cartSummary.subtotalDetails, + ).map((details) => { + if (!details.money?.amount && !details.content) { + return ''; + } + + return ` +
+
${details.title ?? ''}
+ ${getPriceTextTemplate(details, null, 'price')} +
`; + }); + + return lines?.join('') ?? ''; +} + +export const getSubtotalDetailsTemplate = ( + finance: XpsFinance, + cartSummary: CartSummary, +): string => { + return [ + getSubtotalTitle(cartSummary), + getSubtotalContent(finance, cartSummary), + ].join(''); +}; diff --git a/src/features/headless-checkout/web-components/finance-details/tax-translation-map.const.ts b/src/features/headless-checkout/web-components/finance-details/tax-translation-map.const.ts new file mode 100644 index 00000000..0632cd26 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/tax-translation-map.const.ts @@ -0,0 +1,18 @@ +export const taxTranslationMap: Array<[string, string]> = [ + ['vat', 'finance-details-vat'], + ['vat-percent', 'finance-details-vat-percent'], + ['vat-including', 'finance-details-vat-including'], + ['vat-percent-including', 'finance-details-vat-percent-including'], + ['vat-india', 'finance-details-vat-india'], + ['vat-india-percent', 'finance-details-vat-india-percent'], + ['vat-india-including', 'finance-details-vat-india-including'], + [ + 'vat-india-percent-including', + 'finance-details-vat-india-percent-including', + ], + ['vat-ghana', 'finance-details-vat-ghana'], + ['vat-ghana-including', 'finance-details-vat-ghana-including'], + ['sales-tax', 'finance-details-sales-tax'], + ['sales-tax-percent', 'finance-details-sales-tax-percent'], + ['sales-tax-including', 'finance-details-sales-tax-including'], +]; diff --git a/src/features/headless-checkout/web-components/finance-details/total-details.template.spec.ts b/src/features/headless-checkout/web-components/finance-details/total-details.template.spec.ts new file mode 100644 index 00000000..b578b26b --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/total-details.template.spec.ts @@ -0,0 +1,46 @@ +import i18next from 'i18next'; +import { Currency } from '../../../../core/currency/currency.enum'; +import { CartSummary } from '../../../../core/finance-details/cart-summary.interface'; +import { XpsFinance } from '../../../../core/finance-details/xps-finance.interface'; +import { getTotalDetailsTemplate } from './total-details.template'; + +const finance = { + vat: { + percent: 10, + }, +} as unknown as XpsFinance; +const summary = { + totalDetails: [ + { + title: 'title', + money: { + amount: 1, + currency: Currency.USD, + }, + }, + ], +} as unknown as CartSummary; +const summaryWithVatItem = { + totalDetails: [ + { + key: 'vat', + money: { + amount: 1, + currency: Currency.USD, + }, + }, + ], +} as unknown as CartSummary; + +describe('getTotalDetailsTemplate', () => { + it('Should draw title', () => { + const template = getTotalDetailsTemplate(finance, summary); + expect(template).toContain('title'); + }); + + it('Should draw vat', () => { + spyOn(i18next, 't').and.returnValue('vat'); + const template = getTotalDetailsTemplate(finance, summaryWithVatItem); + expect(template).toContain('vat'); + }); +}); diff --git a/src/features/headless-checkout/web-components/finance-details/total-details.template.ts b/src/features/headless-checkout/web-components/finance-details/total-details.template.ts new file mode 100644 index 00000000..6983bbd9 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/total-details.template.ts @@ -0,0 +1,44 @@ +import i18next from 'i18next'; +import { CartLine } from '../../../../core/finance-details/cart-line.interface'; +import { CartSummary } from '../../../../core/finance-details/cart-summary.interface'; +import { XpsFinance } from '../../../../core/finance-details/xps-finance.interface'; +import { getPriceTextTemplate } from './price-text.template'; +import { taxTranslationMap } from './tax-translation-map.const'; + +const translationMap = new Map(taxTranslationMap); + +function translateTotalDetails( + finance: XpsFinance, + items: CartLine[] = [], +): CartLine[] { + return items.map((item) => { + if (!item.key || !translationMap.has(item.key)) { + return item; + } + + return { + ...item, + title: i18next.t(translationMap.get(item.key)!, { + percent: finance?.vat?.percent, + }), + } as CartLine; + }); +} + +export const getTotalDetailsTemplate = ( + finance: XpsFinance, + cartSummary: CartSummary, +): string => { + const totalDetailsRows = translateTotalDetails( + finance, + cartSummary.totalDetails, + )?.map((totalDetails) => { + return ` +
+
${totalDetails.title ?? ''}
+ ${getPriceTextTemplate(null, totalDetails.money)} +
`; + }); + + return totalDetailsRows?.join(''); +}; diff --git a/src/features/headless-checkout/web-components/finance-details/total-row.template.ts b/src/features/headless-checkout/web-components/finance-details/total-row.template.ts new file mode 100644 index 00000000..28b46133 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/total-row.template.ts @@ -0,0 +1,11 @@ +import i18next from 'i18next'; +import { CartSummary } from '../../../../core/finance-details/cart-summary.interface'; +import { getPriceTextTemplate } from './price-text.template'; + +export const getTotalRowTemplate = (cartSummary: CartSummary): string => { + return ` +
+
${i18next.t('finance-details-total-title')}
+ ${getPriceTextTemplate(null, cartSummary.total.money)} +
`; +}; diff --git a/src/features/headless-checkout/web-components/finance-details/total.component.scss b/src/features/headless-checkout/web-components/finance-details/total.component.scss new file mode 100644 index 00000000..2a7724d9 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/total.component.scss @@ -0,0 +1,25 @@ +@use 'src/styles/mixins/typo.mixin' as typo; +@use 'src/styles/mixins/payment-methods.mixin' as payment-methods; +@use 'src/styles/mixins/cart.mixin' as cart; + +psdk-total { + @include typo.psdk-typo; + + & > * { + margin-bottom: 12px; + } + + & > *:last-child { + margin-bottom: 0; + } + + .total-row { + display: flex; + justify-content: space-between; + } + + .title { + margin-right: 8px; + font-weight: 500; + } +} diff --git a/src/features/headless-checkout/web-components/finance-details/total.component.ts b/src/features/headless-checkout/web-components/finance-details/total.component.ts new file mode 100644 index 00000000..e6289959 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/total.component.ts @@ -0,0 +1,80 @@ +import { EventName } from '../../../../core/event-name.enum'; +import { container } from 'tsyringe'; +import { FinanceDetails } from '../../../../core/finance-details/finance-details.interface'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { getFinanceDetailsHandler } from '../../post-messages-handlers/get-finance-details.handler'; +import './finance-details.component.scss'; +import { isEventMessage } from '../../../../core/guards/event-message.guard'; +import { getTotalRowTemplate } from './total-row.template'; +import './total.component.scss'; + +export class TotalComponent extends WebComponentAbstract { + private readonly componentName = 'TotalComponent'; + private readonly headlessCheckout: HeadlessCheckout; + private readonly headlessCheckoutSpy: HeadlessCheckoutSpy; + private readonly window: Window; + private financeDetails?: FinanceDetails | null = null; + + public constructor() { + super(); + this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy); + this.headlessCheckout = container.resolve(HeadlessCheckout); + this.window = container.resolve(Window); + } + + protected connectedCallback(): void { + if (!this.headlessCheckoutSpy.appWasInit) { + this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); + return; + } + this.listenPings(); + + void this.headlessCheckout.events.onCoreEvent( + EventName.financeDetails, + getFinanceDetailsHandler, + (financeDetails) => this.financeDetailsLoadedHandler(financeDetails), + ); + } + + protected getHtml(): string { + if (this.financeDetails) { + return getTotalRowTemplate(this.financeDetails.cartSummary); + } + + return ''; + } + + private readonly financeDetailsLoadedHandler = ( + financeDetails?: FinanceDetails | null, + ): void => { + this.financeDetails = financeDetails; + + this.render(); + }; + + private listenPings(): void { + this.window.addEventListener('message', this.pingCallback); + } + + private readonly pingCallback = (message: MessageEvent): void => { + const data = this.getJsonOrNull(message?.data); + if ( + !data || + !isEventMessage(data) || + data.name !== EventName.requiredComponentPing || + (data.data as { componentName: string }).componentName !== + this.componentName + ) { + return; + } + message.source?.postMessage( + JSON.stringify({ + name: EventName.requiredComponentPong, + data: { componentName: this.componentName }, + }), + message.origin as WindowPostMessageOptions, + ); + }; +} diff --git a/src/features/headless-checkout/web-components/finance-details/transaction-details.template.spec.ts b/src/features/headless-checkout/web-components/finance-details/transaction-details.template.spec.ts new file mode 100644 index 00000000..5ca08e97 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/transaction-details.template.spec.ts @@ -0,0 +1,30 @@ +import i18next from 'i18next'; +import { getTransactionDetailsTemplate } from './transaction-details.template'; +import { CartLine } from '../../../../core/finance-details/cart-line.interface'; + +const invoiceItem = { + key: 'invoice', + title: 'Transaction', + content: '123456789', +} as unknown as CartLine; + +const timeItem = { + key: 'time', + title: 'Date', + content: '2023-10-10T10:10:00+03:00', +} as unknown as CartLine; + +describe('getTransactionDetailsTemplate', () => { + it('Should draw transaction invoice', () => { + spyOn(i18next, 't').and.returnValue('Transaction'); + const template = getTransactionDetailsTemplate([invoiceItem]); + expect(template).toContain('Transaction'); + expect(template).toContain('№123456789'); + }); + + it('Should draw transaction date', () => { + spyOn(i18next, 't').and.returnValue('Date'); + const template = getTransactionDetailsTemplate([timeItem]); + expect(template).toContain('Date'); + }); +}); diff --git a/src/features/headless-checkout/web-components/finance-details/transaction-details.template.ts b/src/features/headless-checkout/web-components/finance-details/transaction-details.template.ts new file mode 100644 index 00000000..f844f9f5 --- /dev/null +++ b/src/features/headless-checkout/web-components/finance-details/transaction-details.template.ts @@ -0,0 +1,66 @@ +import i18next from 'i18next'; +import { container } from 'tsyringe'; +import { CartLine } from '../../../../core/finance-details/cart-line.interface'; +import { DatePipe } from '../../../../core/pipes/date/date.pipe'; + +const translationMap = new Map([ + ['invoice', 'finance-details-transaction-details-invoice'], + ['time', 'finance-details-transaction-details-time'], +]); + +function translateItems(items: CartLine[] = []): CartLine[] { + return items.map((item) => { + if (!item.key || !translationMap.has(item.key)) { + return item; + } + + return { + ...item, + title: i18next.t(translationMap.get(item.key)!), + } as CartLine; + }); +} + +function getItemByKey(items: CartLine[], key: string): CartLine | undefined { + return items.find((item) => item.key === key); +} + +export const getTransactionDetailsTemplate = ( + detailsItems?: CartLine[] +): string => { + if (!detailsItems?.length) { + return ''; + } + + const datePipe = container.resolve(DatePipe); + + const lines: string[] = []; + const items = translateItems(detailsItems); + const invoiceItem = getItemByKey(items, 'invoice'); + const timeItem = getItemByKey(items, 'time'); + + if (invoiceItem?.title && invoiceItem.content) { + lines.push(` +
+
${invoiceItem.title}
+
№${invoiceItem.content}
+
+ `); + } + + if (timeItem?.title && timeItem.content) { + const localDate = datePipe.transform(timeItem.content); + lines.push(` +
+
${timeItem.title}
+
${localDate}
+
+ `); + } + + return ` +
+ ${lines.join('')} +
+ `; +}; diff --git a/src/features/headless-checkout/web-components/get-control-component-config.handler.ts b/src/features/headless-checkout/web-components/get-control-component-config.handler.ts new file mode 100644 index 00000000..2092addd --- /dev/null +++ b/src/features/headless-checkout/web-components/get-control-component-config.handler.ts @@ -0,0 +1,36 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { ControlComponentConfig } from './control-component-config.interface'; +import { Message } from '../../../core/message.interface'; +import { isControlConfigEventMessage } from '../../../core/guards/control-config-event-message.guard'; +import { CheckboxComponentConfig } from './checkbox/checkbox-component-config.interface'; +import { TextComponentConfig } from './text-component/text-component.config.interface'; + +type ControlComponentConfigType = + | ControlComponentConfig + | CheckboxComponentConfig + | TextComponentConfig; + +export const getControlComponentConfigHandler: Handler< + ControlComponentConfigType +> = ( + message: Message, + callback?: (args?: unknown) => boolean | void +): { + isHandled: boolean; + value: ControlComponentConfigType; +} | null => { + if (isControlConfigEventMessage(message)) { + const config = message.data?.config; + + if (typeof callback !== 'function' || !callback(config?.name)) { + return null; + } + + return { + isHandled: true, + value: config!, + }; + } + + return null; +}; diff --git a/src/features/headless-checkout/web-components/legal/legal-component.config.interface.ts b/src/features/headless-checkout/web-components/legal/legal-component.config.interface.ts new file mode 100644 index 00000000..730c2950 --- /dev/null +++ b/src/features/headless-checkout/web-components/legal/legal-component.config.interface.ts @@ -0,0 +1,10 @@ +export interface LegalComponentConfig { + isJapanUser: boolean; + refundPolicyUrl: string; + sctlPolicyUrl?: string; + secureConnection: { + secureConnectionUrl?: string; + isWhiteLabel?: boolean; + }; + disclaimer?: string; +} diff --git a/src/features/headless-checkout/web-components/legal/legal.component.scss b/src/features/headless-checkout/web-components/legal/legal.component.scss new file mode 100644 index 00000000..a266a049 --- /dev/null +++ b/src/features/headless-checkout/web-components/legal/legal.component.scss @@ -0,0 +1,49 @@ +@use 'src/styles/mixins/typo.mixin' as typo; + +psdk-legal { + @include typo.psdk-typo; + + display: flex; + flex-direction: column; + + .disclaimer { + margin-bottom: 4px; + } + + .additional-info { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + } + + a { + color: var(--psdk-font-color); + + &:hover, + &:focus { + text-decoration: none; + } + } + + .company { + display: flex; + flex-wrap: wrap; + + a { + margin-right: 4px; + } + } + + .legal-links { + display: flex; + flex-wrap: wrap; + + a { + margin-right: 10px; + + &:last-child { + margin-right: 0; + } + } + } +} diff --git a/src/features/headless-checkout/web-components/legal/legal.component.spec.ts b/src/features/headless-checkout/web-components/legal/legal.component.spec.ts new file mode 100644 index 00000000..858cfa56 --- /dev/null +++ b/src/features/headless-checkout/web-components/legal/legal.component.spec.ts @@ -0,0 +1,170 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { LegalComponent } from './legal.component'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { EventName } from '../../../../core/event-name.enum'; +import { LegalComponentConfig } from './legal-component.config.interface'; + +function createComponent(): void { + const element = document.createElement(WebComponentTagName.LegalComponent); + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +const mockConfig: LegalComponentConfig = { + isJapanUser: false, + refundPolicyUrl: 'refundPolicyUrl', + secureConnection: { + secureConnectionUrl: 'secureConnectionUrl', + }, +}; + +const delay = async (): Promise => + new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }); + }); + +describe('LegalComponent', () => { + let headlessCheckout: HeadlessCheckout; + let headlessCheckoutSpy: HeadlessCheckoutSpy; + let postMessagesClient: PostMessagesClient; + let windowService: Window; + + window.customElements.define( + WebComponentTagName.LegalComponent, + LegalComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
'; + + headlessCheckout = { + getRegularMethods: noopStub, + } as unknown as HeadlessCheckout; + + headlessCheckoutSpy = { + listenAppInit: noopStub, + get appWasInit() { + return; + }, + } as unknown as HeadlessCheckoutSpy; + + postMessagesClient = { + send: noopStub, + } as unknown as PostMessagesClient; + + windowService = window; + + container.clearInstances(); + + container + .register(HeadlessCheckoutSpy, { + useValue: headlessCheckoutSpy, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }) + .register(Window, { useValue: windowService }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.LegalComponent), + ).toBeDefined(); + }); + + it('Should load legal component config', () => { + const spy = spyOn(postMessagesClient, 'send').and.returnValue( + Promise.resolve({}), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should load legal component config after init', () => { + const spy = spyOn(postMessagesClient, 'send').and.returnValue( + Promise.resolve({}), + ); + const appWasInitSpy = spyOnProperty( + headlessCheckoutSpy, + 'appWasInit', + 'get', + ); + const listenAppInitSpy = spyOn(headlessCheckoutSpy, 'listenAppInit'); + listenAppInitSpy.and.callFake((callback: () => void) => { + appWasInitSpy.and.returnValue(true); + callback(); + }); + appWasInitSpy.and.returnValue(false); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should call addEventListener', async () => { + spyOn(postMessagesClient, 'send').and.returnValue( + Promise.resolve(mockConfig), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + const spy = spyOn(windowService, 'addEventListener'); + createComponent(); + await delay(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should not call addEventListener', async () => { + spyOn(postMessagesClient, 'send').and.returnValue(Promise.resolve(null)); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + const spy = spyOn(windowService, 'addEventListener'); + createComponent(); + await delay(); + expect(spy).not.toHaveBeenCalled(); + }); + + it('Should send pong message', async () => { + spyOn(postMessagesClient, 'send').and.returnValue( + Promise.resolve(mockConfig), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + spyOn(windowService, 'addEventListener').and.callFake( + (name: string, callback: (event: unknown) => void) => { + const messageEvent = { + data: JSON.stringify({ + name: EventName.requiredComponentPing, + data: { componentName: 'LegalComponent' }, + }), + origin: '', + source: { + postMessage: noopStub, + }, + }; + const spy = spyOn(messageEvent.source, 'postMessage'); + callback(messageEvent); + expect(spy).toHaveBeenCalled(); + }, + ); + createComponent(); + await delay(); + }); +}); diff --git a/src/features/headless-checkout/web-components/legal/legal.component.tempate.ts b/src/features/headless-checkout/web-components/legal/legal.component.tempate.ts new file mode 100644 index 00000000..a1610152 --- /dev/null +++ b/src/features/headless-checkout/web-components/legal/legal.component.tempate.ts @@ -0,0 +1,74 @@ +import { LegalComponentConfig } from './legal-component.config.interface'; +import { getSecureConnectionTemplate } from './secure-connection.component.template'; +import i18next from 'i18next'; + +export const getLegalComponentTemplate = ( + config: LegalComponentConfig, +): string => { + const { + isJapanUser, + refundPolicyUrl, + sctlPolicyUrl, + secureConnection, + disclaimer, + } = config; + return ` + ${ + disclaimer + ? ` +
+ ${i18next.t('disclaimer-with-cookies', { + xsolla_terms_link: 'https://xsolla.com/eula', + xsolla_privacy_link: 'https://xsolla.com/cookie', + xsolla_help_link: 'https://help.xsolla.com/', + })} +
` + : '' + } +
+ ${getSecureConnectionTemplate(secureConnection)} + +
`; +}; diff --git a/src/features/headless-checkout/web-components/legal/legal.component.template.spec.ts b/src/features/headless-checkout/web-components/legal/legal.component.template.spec.ts new file mode 100644 index 00000000..5772ba1f --- /dev/null +++ b/src/features/headless-checkout/web-components/legal/legal.component.template.spec.ts @@ -0,0 +1,34 @@ +import { getLegalComponentTemplate } from './legal.component.tempate'; +import { LegalComponentConfig } from './legal-component.config.interface'; + +const mockConfig: LegalComponentConfig = { + isJapanUser: false, + refundPolicyUrl: 'refundPolicyUrl', + sctlPolicyUrl: 'sctlPolicyUrl', + secureConnection: { + isWhiteLabel: true, + secureConnectionUrl: 'secureConnectionUrl', + }, + disclaimer: 'disclaimer', +}; + +const mockConfigJapan: LegalComponentConfig = { + isJapanUser: true, + refundPolicyUrl: 'refundPolicyUrl', + sctlPolicyUrl: 'sctlPolicyUrl', + secureConnection: { + isWhiteLabel: true, + secureConnectionUrl: 'secureConnectionUrl', + }, + disclaimer: 'disclaimer', +}; + +describe('getLegalComponentTemplate', () => { + it('Should contains legal-links', () => { + expect(getLegalComponentTemplate(mockConfig)).toContain('legal-links'); + }); + + it('Should contains sctl-link', () => { + expect(getLegalComponentTemplate(mockConfigJapan)).toContain('sctl-link'); + }); +}); diff --git a/src/features/headless-checkout/web-components/legal/legal.component.ts b/src/features/headless-checkout/web-components/legal/legal.component.ts new file mode 100644 index 00000000..c97af806 --- /dev/null +++ b/src/features/headless-checkout/web-components/legal/legal.component.ts @@ -0,0 +1,94 @@ +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { container } from 'tsyringe'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { EventName } from '../../../../core/event-name.enum'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { getLegalComponentTemplate } from './legal.component.tempate'; +import { Message } from '../../../../core/message.interface'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { getLegalComponentConfigHandler } from '../../post-messages-handlers/get-legal-component-config.handler'; +import { LegalComponentConfig } from './legal-component.config.interface'; +import { isEventMessage } from '../../../../core/guards/event-message.guard'; +import './legal.component.scss'; + +export class LegalComponent extends WebComponentAbstract { + private readonly componentName = 'LegalComponent'; + private readonly postMessagesClient: PostMessagesClient; + private readonly headlessCheckout: HeadlessCheckout; + private readonly headlessCheckoutSpy: HeadlessCheckoutSpy; + private readonly window: Window; + private config?: LegalComponentConfig; + + public constructor() { + super(); + this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy); + this.headlessCheckout = container.resolve(HeadlessCheckout); + this.postMessagesClient = container.resolve(PostMessagesClient); + this.window = container.resolve(Window); + } + + protected connectedCallback(): void { + if (!this.headlessCheckoutSpy.appWasInit) { + this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); + return; + } + void this.getLegalComponentConfig().then(this.configLoadedHandler); + } + + protected readonly configLoadedHandler = ( + config: LegalComponentConfig, + ): void => { + this.config = config; + super.render(); + if (this.config) { + this.listenPings(); + } + }; + + protected listenPings(): void { + this.window.addEventListener('message', this.pingCallback); + } + + protected disconnectedCallback(): void { + super.disconnectedCallback(); + this.window.removeEventListener('message', this.pingCallback); + } + + protected async getLegalComponentConfig(): Promise { + const msg: Message = { + name: EventName.getLegalComponentConfig, + }; + + return this.postMessagesClient.send( + msg, + getLegalComponentConfigHandler, + ) as Promise; + } + + protected getHtml(): string { + if (this.config) { + return getLegalComponentTemplate(this.config); + } + return ''; + } + + private readonly pingCallback = (message: MessageEvent): void => { + const data = this.getJsonOrNull(message?.data); + if ( + !data || + !isEventMessage(data) || + data.name !== EventName.requiredComponentPing || + (data.data as { componentName: string }).componentName !== + this.componentName + ) { + return; + } + message.source?.postMessage( + JSON.stringify({ + name: EventName.requiredComponentPong, + data: { componentName: this.componentName }, + }), + message.origin as WindowPostMessageOptions, + ); + }; +} diff --git a/src/features/headless-checkout/web-components/legal/secure-connection.component.template.spec.ts b/src/features/headless-checkout/web-components/legal/secure-connection.component.template.spec.ts new file mode 100644 index 00000000..f20bf9ae --- /dev/null +++ b/src/features/headless-checkout/web-components/legal/secure-connection.component.template.spec.ts @@ -0,0 +1,27 @@ +import { LegalComponentConfig } from './legal-component.config.interface'; +import { getSecureConnectionTemplate } from './secure-connection.component.template'; + +const mockSecureConnection: LegalComponentConfig['secureConnection'] = { + isWhiteLabel: false, + secureConnectionUrl: 'secureConnectionUrl', +}; + +const mockSecureConnectionWhiteLabel: LegalComponentConfig['secureConnection'] = + { + isWhiteLabel: true, + secureConnectionUrl: 'secureConnectionUrl', + }; + +describe('getSecureConnectionTemplate', () => { + it('Should contains legal-links', () => { + expect(getSecureConnectionTemplate(mockSecureConnection)).toContain( + 'logo-link' + ); + }); + + it('Should not draw link if is white label', () => { + expect( + getSecureConnectionTemplate(mockSecureConnectionWhiteLabel) + ).not.toContain('logo-link'); + }); +}); diff --git a/src/features/headless-checkout/web-components/legal/secure-connection.component.template.ts b/src/features/headless-checkout/web-components/legal/secure-connection.component.template.ts new file mode 100644 index 00000000..33bd4fda --- /dev/null +++ b/src/features/headless-checkout/web-components/legal/secure-connection.component.template.ts @@ -0,0 +1,39 @@ +import i18next from 'i18next'; +import { LegalComponentConfig } from './legal-component.config.interface'; +import { cdnIconsUrl } from '../../environment'; + +const logo = `${cdnIconsUrl}/logo.svg`; + +export const getSecureConnectionTemplate = ( + secureConnection?: LegalComponentConfig['secureConnection'], +): string => { + const isWhiteLabel = secureConnection?.isWhiteLabel; + const secureConnectionUrl = secureConnection?.secureConnectionUrl; + + return ` +
+
+ ${ + !isWhiteLabel && secureConnectionUrl + ? ` + + ` + : ` + + ` + } +
+ ${i18next.t('secure-connection')} +
+
+
+`; +}; diff --git a/src/features/headless-checkout/web-components/pages/google-pay/google-pay-button-color.type.ts b/src/features/headless-checkout/web-components/pages/google-pay/google-pay-button-color.type.ts new file mode 100644 index 00000000..ed03e02e --- /dev/null +++ b/src/features/headless-checkout/web-components/pages/google-pay/google-pay-button-color.type.ts @@ -0,0 +1 @@ +export type GooglePayButtonColorType = 'default' | 'white' | 'black'; diff --git a/src/features/headless-checkout/web-components/pages/google-pay/google-pay-button.component.spec.ts b/src/features/headless-checkout/web-components/pages/google-pay/google-pay-button.component.spec.ts new file mode 100644 index 00000000..40d2c592 --- /dev/null +++ b/src/features/headless-checkout/web-components/pages/google-pay/google-pay-button.component.spec.ts @@ -0,0 +1,34 @@ +import { WebComponentTagName } from '../../../../../core/web-components/web-component-tag-name.enum'; +import { GooglePayButtonComponent } from '../../../../../web-components'; + +function createComponent(): void { + const element = document.createElement( + WebComponentTagName.GooglePayButtonComponent, + ); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +describe('GooglePayButtonComponent', () => { + window.customElements.define( + WebComponentTagName.GooglePayButtonComponent, + GooglePayButtonComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
'; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create iframe', () => { + createComponent(); + + const iframe = document.querySelector('iframe'); + + expect((iframe as HTMLElement).getAttribute('src')).toContain( + 'google-pay-button', + ); + }); +}); diff --git a/src/features/headless-checkout/web-components/pages/google-pay/google-pay-button.component.ts b/src/features/headless-checkout/web-components/pages/google-pay/google-pay-button.component.ts new file mode 100644 index 00000000..996f0350 --- /dev/null +++ b/src/features/headless-checkout/web-components/pages/google-pay/google-pay-button.component.ts @@ -0,0 +1,20 @@ +import { SecureComponentAbstract } from '../../../../../core/web-components/secure-component/secure-component.abstract'; +import { headlessCheckoutAppUrl } from '../../../environment'; +import { GooglePayButtonColorType } from './google-pay-button-color.type'; + +export class GooglePayButtonComponent extends SecureComponentAbstract { + protected componentName = 'pages/google-pay-button'; + private buttonColor: GooglePayButtonColorType = 'default'; + + public setButtonColor(color: GooglePayButtonColorType): void { + this.buttonColor = color; + } + + protected getHtml(): string { + return this.getSecureHtml(); + } + + protected getSecureHtml(): string { + return ``; + } +} diff --git a/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component.scss b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component.scss new file mode 100644 index 00000000..97e6a86b --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component.scss @@ -0,0 +1,25 @@ +@use 'src/styles/mixins/typo.mixin' as typo; + +psdk-payment-form-messages { + @include typo.psdk-typo; + + .form-message { + margin: 0; + padding: 12px; + border-radius: var(--psdk-common-border-radius); + background: var(--psdk-neutral-bg); + color: var(--psdk-neutral-color); + + &:nth-child(n) { + margin-bottom: 4px; + } + + &:last-child { + margin-bottom: 0; + } + + & a { + color: var(--psdk-neutral-color); + } + } +} diff --git a/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component.spec.ts b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component.spec.ts new file mode 100644 index 00000000..69fd6fb2 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component.spec.ts @@ -0,0 +1,90 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { PaymentFormMessagesComponent } from './payment-form-messages.component'; +import { HeadlessCheckoutMock } from '../../../../tests/stubs/headless-checkout.mock'; +import { HeadlessCheckout } from '../../headless-checkout'; + +function createComponent(): void { + const element = document.createElement( + WebComponentTagName.PaymentFormMessages + ); + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +describe('PaymentFormMessagesComponent', () => { + let headlessCheckout: HeadlessCheckoutMock; + let headlessCheckoutSpy: HeadlessCheckoutSpy; + + window.customElements.define( + WebComponentTagName.PaymentFormMessages, + PaymentFormMessagesComponent + ); + + beforeEach(() => { + document.body.innerHTML = '
'; + + headlessCheckout = new HeadlessCheckoutMock(); + headlessCheckoutSpy = { + listenAppInit: noopStub, + get appWasInit() { + return; + }, + } as unknown as HeadlessCheckoutSpy; + + container.clearInstances(); + + container + .register(HeadlessCheckoutSpy, { + useValue: headlessCheckoutSpy, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout as unknown as HeadlessCheckout, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.PaymentFormMessages) + ).toBeDefined(); + }); + + it('Should subscribe to form messages event', () => { + const spy = spyOn(headlessCheckout.events, 'onCoreEvent'); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should not draw for empty form messages event', () => { + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + headlessCheckout.emitEvent(null); + const element = document.querySelector( + WebComponentTagName.PaymentFormMessages + ); + expect(element!.innerHTML).toEqual(''); + }); + + it('Should draw on finance details event', () => { + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + headlessCheckout.emitEvent(['message1']); + expect( + document.querySelector(WebComponentTagName.PaymentFormMessages) + ).not.toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component.ts b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component.ts new file mode 100644 index 00000000..73d5328c --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component.ts @@ -0,0 +1,48 @@ +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { container } from 'tsyringe'; +import { EventName } from '../../../../core/event-name.enum'; +import { getFormMessagesHandler } from '../../post-messages-handlers/get-form-messages.handler'; +import { getPaymentFormMessagesTemplate } from './payment-form-messages.template'; +import './payment-form-messages.component.scss'; + +export class PaymentFormMessagesComponent extends WebComponentAbstract { + private readonly headlessCheckout: HeadlessCheckout; + private readonly headlessCheckoutSpy: HeadlessCheckoutSpy; + private messages?: string[]; + + public constructor() { + super(); + this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy); + this.headlessCheckout = container.resolve(HeadlessCheckout); + } + + protected connectedCallback(): void { + if (!this.headlessCheckoutSpy.appWasInit) { + this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); + return; + } + void this.headlessCheckout.events.onCoreEvent( + EventName.formMessagesChanged, + getFormMessagesHandler, + (messages) => this.messagesLoadedHandler(messages), + ); + } + + protected getHtml(): string { + if (!this.messages) { + return ''; + } + return getPaymentFormMessagesTemplate(this.messages); + } + + private readonly messagesLoadedHandler = (messages?: string[]): void => { + if (!messages) { + return; + } + this.messages = messages; + + this.render(); + }; +} diff --git a/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.template.spec.ts b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.template.spec.ts new file mode 100644 index 00000000..e4b43714 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.template.spec.ts @@ -0,0 +1,11 @@ +import { getPaymentFormMessagesTemplate } from './payment-form-messages.template'; + +const mockMessages = ['message1', 'message2']; + +describe('getPaymentFormMessagesTemplate', () => { + it('Should contains payment-form-messages', () => { + expect(getPaymentFormMessagesTemplate(mockMessages)).toContain( + 'payment-form-messages' + ); + }); +}); diff --git a/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.template.ts b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.template.ts new file mode 100644 index 00000000..9c19603c --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form-messages/payment-form-messages.template.ts @@ -0,0 +1,6 @@ +export const getPaymentFormMessagesTemplate = (messages: string[]): string => { + const messagesHtml = messages.map( + (message) => `

${message}

` + ); + return `
${messagesHtml.join('')}
`; +}; diff --git a/src/features/headless-checkout/web-components/payment-form/field-settings.interface.ts b/src/features/headless-checkout/web-components/payment-form/field-settings.interface.ts new file mode 100644 index 00000000..c90a1f23 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/field-settings.interface.ts @@ -0,0 +1,4 @@ +export interface FieldSettings { + name: string; + type: string; +} diff --git a/src/features/headless-checkout/web-components/payment-form/form-controls-tags.list.ts b/src/features/headless-checkout/web-components/payment-form/form-controls-tags.list.ts new file mode 100644 index 00000000..bd181b2e --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/form-controls-tags.list.ts @@ -0,0 +1,9 @@ +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; + +export const formControlsTags = [ + WebComponentTagName.TextComponent, + WebComponentTagName.PhoneComponent, + WebComponentTagName.SelectComponent, + WebComponentTagName.CheckboxComponent, + WebComponentTagName.CardNumberComponent, +]; diff --git a/src/features/headless-checkout/web-components/payment-form/get-invalid-fields.function.spec.ts b/src/features/headless-checkout/web-components/payment-form/get-invalid-fields.function.spec.ts new file mode 100644 index 00000000..07823155 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/get-invalid-fields.function.spec.ts @@ -0,0 +1,36 @@ +import { getInvalidFields } from './get-invalid-fields.function'; + +const expectedFields = [ + { name: 'zip', type: 'text' }, + { name: 'email', type: 'text' }, +]; + +describe('getInvalidFields', () => { + it('Should return invalid fields', () => { + const exists = [ + { name: 'zip', type: 'text' }, + { name: 'card', type: 'text' }, + ]; + expect(getInvalidFields(expectedFields, exists)).toEqual([ + { name: 'card', type: 'text' }, + ]); + }); + + it('Should return invalid fields if duplicates', () => { + const exists = [ + { name: 'zip', type: 'text' }, + { name: 'zip', type: 'text' }, + ]; + expect(getInvalidFields(expectedFields, exists)).toEqual([ + { name: 'zip', type: 'text' }, + ]); + }); + + it('Should not filter empty name fields', () => { + const exists = [ + { name: 'zip', type: 'text' }, + { name: '', type: 'text' }, + ]; + expect(getInvalidFields(expectedFields, exists)).toEqual([]); + }); +}); diff --git a/src/features/headless-checkout/web-components/payment-form/get-invalid-fields.function.ts b/src/features/headless-checkout/web-components/payment-form/get-invalid-fields.function.ts new file mode 100644 index 00000000..f5ce9a95 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/get-invalid-fields.function.ts @@ -0,0 +1,42 @@ +import { FieldSettings } from './field-settings.interface'; + +export function getInvalidFields( + expectedFieldsNames: FieldSettings[], + existsControlsNames: FieldSettings[], +): FieldSettings[] { + const fieldsExistsCountMap: { [k: string]: number } = {}; + for (const { name } of existsControlsNames) { + if (name) { + const count = fieldsExistsCountMap[name]; + if (!count) { + fieldsExistsCountMap[name] = 1; + } else { + fieldsExistsCountMap[name] = count + 1; + } + } + } + + const invalidFieldNames: FieldSettings[] = []; + + // eslint-disable-next-line prefer-const + for (let [name, count] of Object.entries(fieldsExistsCountMap)) { + const invalidField = existsControlsNames.find( + (field) => field.name === name, + ); + + if (!invalidField) { + continue; + } + + if (!expectedFieldsNames.some((field) => field.name === name)) { + invalidFieldNames.push(invalidField); + } else if (count > 1) { + while (count > 1) { + invalidFieldNames.push(invalidField); + count--; + } + } + } + + return invalidFieldNames; +} diff --git a/src/features/headless-checkout/web-components/payment-form/get-missed-fields.function.spec.ts b/src/features/headless-checkout/web-components/payment-form/get-missed-fields.function.spec.ts new file mode 100644 index 00000000..7e017e08 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/get-missed-fields.function.spec.ts @@ -0,0 +1,19 @@ +import { getMissedFields } from './get-missed-fields.function'; +import { FieldSettings } from './field-settings.interface'; + +const expectedFields = [ + { name: 'zip', type: 'text' }, + { name: 'email', type: 'text' }, +]; + +describe('getMissedFields', () => { + it('Should return missed fields', () => { + const exists: FieldSettings[] = [ + { name: 'zip', type: 'text' }, + { name: 'card', type: 'text' }, + ]; + expect(getMissedFields(expectedFields, exists)).toEqual([ + { name: 'email', type: 'text' }, + ]); + }); +}); diff --git a/src/features/headless-checkout/web-components/payment-form/get-missed-fields.function.ts b/src/features/headless-checkout/web-components/payment-form/get-missed-fields.function.ts new file mode 100644 index 00000000..69e8a3a0 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/get-missed-fields.function.ts @@ -0,0 +1,22 @@ +import { FieldSettings } from './field-settings.interface'; + +export function getMissedFields( + expectedFieldsNamesAndTypes: FieldSettings[], + existsControlsNamesAndTypes: FieldSettings[], +): FieldSettings[] { + const fieldsExistsMap: { [k: string]: boolean } = {}; + + for (const field of existsControlsNamesAndTypes) { + if (field) { + fieldsExistsMap[field.name] = true; + } + } + + const missedFieldsNames = []; + for (const field of expectedFieldsNamesAndTypes) { + if (!fieldsExistsMap[field.name]) { + missedFieldsNames.push(field); + } + } + return missedFieldsNames; +} diff --git a/src/features/headless-checkout/web-components/payment-form/get-web-component-by-field-name.function.ts b/src/features/headless-checkout/web-components/payment-form/get-web-component-by-field-name.function.ts new file mode 100644 index 00000000..e638a381 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/get-web-component-by-field-name.function.ts @@ -0,0 +1,25 @@ +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { FieldSettings } from './field-settings.interface'; + +export const getWebComponent = (field: FieldSettings): WebComponentTagName => { + if (field.type === 'text' && field.name === 'card_number') { + return WebComponentTagName.CardNumberComponent; + } + + if (field.type === 'text' && field.name === 'phone') { + return WebComponentTagName.PhoneComponent; + } + if (field.type === 'text') { + return WebComponentTagName.TextComponent; + } + + if (field.type === 'select') { + return WebComponentTagName.SelectComponent; + } + + if (field.type === 'check') { + return WebComponentTagName.CheckboxComponent; + } + + return WebComponentTagName.TextComponent; +}; diff --git a/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.spec.ts b/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.spec.ts new file mode 100644 index 00000000..48614669 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.spec.ts @@ -0,0 +1,76 @@ +import 'reflect-metadata'; +import { container } from 'tsyringe'; +import { PaymentFormFieldsService } from './payment-form-fields.service'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; + +const getTextInputElements = (names: string[]): NodeListOf => { + const mockContainer = document.createElement('div'); + for (const name of names) { + const mockElement = document.createElement( + WebComponentTagName.TextComponent, + ); + mockElement.setAttribute('name', name); + mockContainer.appendChild(mockElement); + } + + return mockContainer.querySelectorAll(WebComponentTagName.TextComponent); +}; + +describe('PaymentFormFieldsManager', () => { + let paymentFormFieldsManager: PaymentFormFieldsService; + let windowService: Window; + + beforeEach(() => { + windowService = window; + container.clearInstances(); + + container.register(Window, { useValue: windowService }); + paymentFormFieldsManager = container + .createChildContainer() + .resolve(PaymentFormFieldsService); + }); + + it('Should create missed fields and append them into body', () => { + const textInputElements = getTextInputElements([]); + spyOn(windowService.document, 'querySelectorAll').and.returnValue( + textInputElements, + ); + const missedFields = [{ name: 'card', type: 'text' }]; + const mockBody = { + appendChild: noopStub, + } as unknown as HTMLElement; + spyOn(windowService.document, 'querySelector').and.returnValue( + mockBody as unknown as Element, + ); + const spy = spyOn(mockBody, 'appendChild'); + paymentFormFieldsManager.createMissedFields(missedFields, mockBody); + expect(spy).toHaveBeenCalled(); + }); + + it('Should remove extra field', () => { + const extraFields = [{ name: 'card', type: 'text' }]; + const mockElement = { + remove: noopStub, + }; + spyOn(windowService.document, 'querySelector').and.returnValue( + mockElement as unknown as Element, + ); + const spy = spyOn(mockElement, 'remove'); + paymentFormFieldsManager.removeExtraFields(extraFields); + expect(spy).toHaveBeenCalled(); + }); + + it('Should remove fields with empty name', () => { + const textInputElements = getTextInputElements(['zip']); + textInputElements.forEach((element) => { + element.removeAttribute('name'); + }); + spyOn(windowService.document, 'querySelectorAll').and.returnValue( + textInputElements, + ); + const spy = spyOn(textInputElements[0], 'remove'); + paymentFormFieldsManager.removeEmptyNameFields(); + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.ts b/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.ts new file mode 100644 index 00000000..6382b33e --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.ts @@ -0,0 +1,46 @@ +import { injectable } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { getWebComponent } from './get-web-component-by-field-name.function'; +import { FieldSettings } from './field-settings.interface'; + +@injectable() +export class PaymentFormFieldsService { + public constructor(private readonly window: Window) {} + + public createMissedFields( + missedFields: FieldSettings[], + container: HTMLElement, + ): void { + const documentFragment = this.window.document.createDocumentFragment(); + + for (const field of missedFields) { + const formElement = this.window.document.createElement( + getWebComponent(field), + ); + formElement.setAttribute('name', field.name); + documentFragment.append(formElement); + } + + container.appendChild(documentFragment); + } + + public removeExtraFields(extraFields: FieldSettings[]): void { + for (const field of extraFields) { + const formElement = this.window.document.querySelector( + `[name=${field.name}]`, + ); + formElement?.remove(); + } + } + + public removeEmptyNameFields(): void { + const formInputs = this.window.document.querySelectorAll( + WebComponentTagName.TextComponent, + ); + Array.from(formInputs).forEach((formInput) => { + if (!formInput.getAttribute('name')) { + formInput.remove(); + } + }); + } +} diff --git a/src/features/headless-checkout/web-components/payment-form/payment-form.component.spec.ts b/src/features/headless-checkout/web-components/payment-form/payment-form.component.spec.ts new file mode 100644 index 00000000..28e0657a --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/payment-form.component.spec.ts @@ -0,0 +1,203 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { PaymentFormFieldsService } from './payment-form-fields.service'; +import { PaymentFormComponent } from './payment-form.component'; +import { Field } from '../../../../core/form/field.interface'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { FieldSettings } from './field-settings.interface'; +import { FormLoader } from '../../../../core/form/form-loader'; + +function createComponent(): void { + const element = document.createElement( + WebComponentTagName.PaymentFormComponent, + ); + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +const mockFormFields: Field[] = [ + { + name: 'zip', + type: 'text', + }, + { + name: 'card', + type: 'text', + }, +] as unknown as Field[]; + +const getTextInputElements = (names: string[]): NodeListOf => { + const mockContainer = document.createElement('div'); + for (const name of names) { + const mockElement = document.createElement( + WebComponentTagName.TextComponent, + ); + mockElement.setAttribute('name', name); + mockContainer.appendChild(mockElement); + } + + return mockContainer.querySelectorAll(WebComponentTagName.TextComponent); +}; + +describe('PaymentFormComponent', () => { + let headlessCheckout: HeadlessCheckout; + let formSpy: FormSpy; + let paymentFormFieldsManager: PaymentFormFieldsService; + let postMessagesClient: PostMessagesClient; + let windowService: Window; + let formLoader: FormLoader; + + window.customElements.define( + WebComponentTagName.PaymentFormComponent, + PaymentFormComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
'; + + headlessCheckout = { + events: { + send: noopStub, + onCoreEvent: noopStub, + }, + form: { + onFieldsStatusChange: noopStub, + onNextAction: noopStub, + }, + } as unknown as HeadlessCheckout; + + formSpy = { + listenFormInit: noopStub, + get formWasInit() { + return; + }, + get formFields() { + return; + }, + } as unknown as FormSpy; + + formLoader = { + setupAndAwaitFieldsLoading: noopStub, + setFieldLoaded: noopStub, + } as unknown as FormLoader; + + paymentFormFieldsManager = { + createMissedFields: noopStub, + removeExtraFields: noopStub, + removeEmptyNameFields: noopStub, + } as unknown as PaymentFormFieldsService; + + postMessagesClient = { + send: noopStub, + } as unknown as PostMessagesClient; + + windowService = window; + + container.clearInstances(); + + container + .register(FormSpy, { + useValue: formSpy, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }) + .register(PaymentFormFieldsService, { + useValue: paymentFormFieldsManager, + }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }) + .register(Window, { useValue: windowService }) + .register(FormLoader, { useValue: formLoader }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.PaymentFormComponent), + ).toBeDefined(); + }); + + it('Should load form fields', () => { + const spy = spyOnProperty(formSpy, 'formFields', 'get').and.returnValue([]); + spyOnProperty(formSpy, 'formWasInit', 'get').and.returnValue(true); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should load form fields after init', () => { + const spy = spyOnProperty(formSpy, 'formFields', 'get').and.returnValue([]); + const formWasInitSpy = spyOnProperty(formSpy, 'formWasInit', 'get'); + const listenFormInitSpy = spyOn(formSpy, 'listenFormInit'); + listenFormInitSpy.and.callFake((callback: () => void) => { + formWasInitSpy.and.returnValue(true); + callback(); + }); + formWasInitSpy.and.returnValue(false); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should create missed form fields', () => { + spyOnProperty(formSpy, 'formFields', 'get').and.returnValue(mockFormFields); + spyOnProperty(formSpy, 'formWasInit', 'get').and.returnValue(true); + + const textInputElements = getTextInputElements(['zip']); + spyOn(windowService.document, 'querySelectorAll').and.returnValue( + textInputElements, + ); + + const spy = spyOn(paymentFormFieldsManager, 'createMissedFields'); + createComponent(); + + expect(spy).toHaveBeenCalled(); + }); + + it('Should not call createMissedFields if no fields', () => { + spyOnProperty(formSpy, 'formFields', 'get').and.returnValue(undefined); + spyOnProperty(formSpy, 'formWasInit', 'get').and.returnValue(true); + + const spy = spyOn(paymentFormFieldsManager, 'createMissedFields'); + createComponent(); + + expect(spy).not.toHaveBeenCalled(); + }); + + it('Should remove extra form fields', () => { + spyOnProperty(formSpy, 'formFields', 'get').and.returnValue(mockFormFields); + spyOnProperty(formSpy, 'formWasInit', 'get').and.returnValue(true); + + const textInputElements = getTextInputElements(['zip', 'zip']); + spyOn(windowService.document, 'querySelectorAll').and.returnValue( + textInputElements, + ); + + const spy = spyOn(paymentFormFieldsManager, 'removeExtraFields'); + createComponent(); + + const zip: FieldSettings = { + name: 'zip', + type: 'text', + }; + // rewrite querySelectorAll mock + expect(spy).toHaveBeenCalledWith([ + zip, + zip, + zip, + zip, + zip, + zip, + zip, + zip, + zip, + ]); + }); +}); diff --git a/src/features/headless-checkout/web-components/payment-form/payment-form.component.ts b/src/features/headless-checkout/web-components/payment-form/payment-form.component.ts new file mode 100644 index 00000000..0db94c2b --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/payment-form.component.ts @@ -0,0 +1,160 @@ +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { container } from 'tsyringe'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { Field } from '../../../../core/form/field.interface'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { EventName } from '../../../../core/event-name.enum'; +import { getMissedFields } from './get-missed-fields.function'; +import { getInvalidFields } from './get-invalid-fields.function'; +import { PaymentFormFieldsService } from './payment-form-fields.service'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { formControlsTags } from './form-controls-tags.list'; +import { isShowFieldsAction } from '../../../../core/actions/is-show-fields-action.function'; +import { FieldSettings } from './field-settings.interface'; + +export class PaymentFormComponent extends WebComponentAbstract { + private readonly headlessCheckout: HeadlessCheckout; + private readonly formSpy: FormSpy; + private readonly paymentFormFieldsManager: PaymentFormFieldsService; + private readonly window: Window; + + private get elementRef(): HTMLElement { + return this.querySelector('div')! as HTMLElement; + } + + public constructor() { + super(); + this.headlessCheckout = container.resolve(HeadlessCheckout); + this.formSpy = container.resolve(FormSpy); + this.window = container.resolve(Window); + this.paymentFormFieldsManager = container.resolve(PaymentFormFieldsService); + } + + protected connectedCallback(): void { + if (!this.formSpy.formWasInit) { + this.formSpy.listenFormInit(() => this.connectedCallback()); + return; + } + const formExpectedFields = this.formSpy.formFields; + + const formRequiredFields = this.getRequriedFields(this.formSpy.formFields); + + super.render(); + if (formExpectedFields) { + this.setupFormLoader(formRequiredFields); + + const expectedFields = this.getFieldsSettings(formExpectedFields); + const requiredFields = this.getFieldsSettings(formRequiredFields); + const existsControls = this.getExistsControls(); + + this.setupFormFields(expectedFields, requiredFields, existsControls); + this.listenFormInit(); + } + } + + protected getHtml(): string { + return '
'; + } + + private listenFormInit(): void { + this.headlessCheckout.form.onNextAction((nextAction) => { + if (isShowFieldsAction(nextAction)) { + this.formSpy.formFields = nextAction.data.fields; + this.connectedCallback(); + } + }); + } + + private setupFormFields( + expectedFields: FieldSettings[], + requiredFields: FieldSettings[], + existsControls: FieldSettings[], + ): void { + const missedFields = getMissedFields(requiredFields, existsControls); + this.setupMissedFields(missedFields); + + const invalidFields = getInvalidFields(expectedFields, existsControls); + this.setupInvalidFields(invalidFields); + } + + private setupMissedFields(missedFields: FieldSettings[]): void { + this.paymentFormFieldsManager.createMissedFields( + missedFields, + this.elementRef, + ); + this.logMissedFields(missedFields); + } + + private setupInvalidFields(missedFields: FieldSettings[]): void { + this.paymentFormFieldsManager.removeExtraFields(missedFields); + this.paymentFormFieldsManager.removeEmptyNameFields(); + this.logExtraFields(missedFields); + } + + private getRequriedFields(fields: Field[] = []): Field[] { + return fields.filter((field) => field.isMandatory === '1'); + } + + private getFieldsSettings(fields: Field[]): FieldSettings[] { + return fields.map((field) => ({ name: field.name, type: field.type })); + } + + private getExistsControls(): FieldSettings[] { + const existsControlsNames: FieldSettings[] = []; + + formControlsTags.forEach((tag: WebComponentTagName) => { + const formInputs = this.window.document.querySelectorAll(tag); + + const controlsNames = Array.from(formInputs).map((formInput) => + formInput.getAttribute('name'), + ); + + controlsNames.forEach((name: string | null) => { + const existField = this.formSpy.formFields?.find( + (field) => field.name === name, + ); + + if (existField) { + existsControlsNames.push({ + name: existField.name, + type: existField.type, + }); + } + }); + }); + + return existsControlsNames; + } + + private logMissedFields(missedFields: FieldSettings[]): void { + if (!missedFields.length) { + return; + } + const message = `This fields were auto created: [${missedFields + .map((field) => field.name) + .join(', ')}]. They are mandatory for a payment flow`; + console.warn(message); + void this.headlessCheckout.events.send<{ message: string }>( + { name: EventName.warning, data: { message } }, + () => null, + ); + } + + private logExtraFields(extraFields: FieldSettings[]): void { + if (!extraFields.length) { + return; + } + const message = `This fields were auto removed: [${extraFields + .map((field) => field.name) + .join(', ')}]. They are useless for a payment flow`; + console.warn(message); + void this.headlessCheckout.events.send<{ message: string }>( + { name: EventName.warning, data: { message } }, + () => null, + ); + } + + private setupFormLoader(fields: Field[]): void { + void this.formLoader.setupAndAwaitFieldsLoading(fields); + } +} diff --git a/src/features/headless-checkout/web-components/payment-form/web-components-fields-names.map.ts b/src/features/headless-checkout/web-components/payment-form/web-components-fields-names.map.ts new file mode 100644 index 00000000..b27c9a0e --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-form/web-components-fields-names.map.ts @@ -0,0 +1,8 @@ +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; + +export const webComponentsFieldsNamesMap: { [k: string]: WebComponentTagName } = + { + phone: WebComponentTagName.PhoneComponent, + select: WebComponentTagName.SelectComponent, + allowSubscription: WebComponentTagName.CheckboxComponent, + }; diff --git a/src/features/headless-checkout/web-components/payment-methods/filter-payment-methods.function.spec.ts b/src/features/headless-checkout/web-components/payment-methods/filter-payment-methods.function.spec.ts new file mode 100644 index 00000000..f12a4408 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/filter-payment-methods.function.spec.ts @@ -0,0 +1,38 @@ +import { filterPaymentMethods } from './filter-payment-methods.function'; +import { PaymentMethod } from '../../../../core/payment-method.interface'; + +const mockCard = { + name: 'Bank card', +} as PaymentMethod; + +const mockQiwi = { + name: 'Qiwi', +} as PaymentMethod; + +const paymentMethods: PaymentMethod[] = [mockCard, mockQiwi]; + +describe('filterPaymentMethods', () => { + it('Should return qiwi', () => { + const searchValue = 'qiwi'; + expect(filterPaymentMethods(paymentMethods, searchValue)).toEqual([ + mockQiwi, + ]); + }); + + it('Should return bank card', () => { + const searchValue = 'card'; + expect(filterPaymentMethods(paymentMethods, searchValue)).toEqual([ + mockCard, + ]); + }); + + it('Should return empty array', () => { + const searchValue = 'paypal'; + expect(filterPaymentMethods(paymentMethods, searchValue)).toEqual([]); + }); + + it('Should return undefined', () => { + const searchValue = 'paypal'; + expect(filterPaymentMethods(undefined, searchValue)).toBeUndefined(); + }); +}); diff --git a/src/features/headless-checkout/web-components/payment-methods/filter-payment-methods.function.ts b/src/features/headless-checkout/web-components/payment-methods/filter-payment-methods.function.ts new file mode 100644 index 00000000..73b83547 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/filter-payment-methods.function.ts @@ -0,0 +1,10 @@ +import { PaymentMethod } from '../../../../core/payment-method.interface'; + +export const filterPaymentMethods = ( + paymentMethods: PaymentMethod[] | undefined, + searchValue: string +): PaymentMethod[] | undefined => { + return paymentMethods?.filter((paymentMethod: PaymentMethod) => + paymentMethod.name.toLowerCase().includes(String(searchValue).toLowerCase()) + ); +}; diff --git a/src/features/headless-checkout/web-components/payment-methods/payment-method-attributes.enum.ts b/src/features/headless-checkout/web-components/payment-methods/payment-method-attributes.enum.ts new file mode 100644 index 00000000..7d6f0de6 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/payment-method-attributes.enum.ts @@ -0,0 +1,3 @@ +export enum PaymentMethodAttributes { + methodId = 'data-method-id', +} diff --git a/src/features/headless-checkout/web-components/payment-methods/payment-method.template.spec.ts b/src/features/headless-checkout/web-components/payment-methods/payment-method.template.spec.ts new file mode 100644 index 00000000..819952cb --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/payment-method.template.spec.ts @@ -0,0 +1,14 @@ +import { PaymentMethod } from '../../../../core/payment-method.interface'; +import { getPaymentMethodTemplate } from './payment-method.template'; + +const mockQiwi = { + name: 'Qiwi', + iconName: 'qiwi.svg', + id: 16, +} as PaymentMethod; + +describe('getPaymentMethodTemplate', () => { + it('Should draw image', () => { + expect(getPaymentMethodTemplate(mockQiwi)).toContain('img'); + }); +}); diff --git a/src/features/headless-checkout/web-components/payment-methods/payment-method.template.ts b/src/features/headless-checkout/web-components/payment-methods/payment-method.template.ts new file mode 100644 index 00000000..b3594c4f --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/payment-method.template.ts @@ -0,0 +1,29 @@ +import { PaymentMethod } from '../../../../core/payment-method.interface'; +import { cdnIconsUrl } from '../../environment'; + +const iconsPath = `${cdnIconsUrl}/brand-logos`; +const defaultIconPath = `${cdnIconsUrl}/default-payment-icons/default.svg`; + +const arrow = `${cdnIconsUrl}/common-icons/chevron-right--line.svg`; +export const getPaymentMethodTemplate = (method: PaymentMethod): string => { + let iconPath: string; + + if (!method.iconName) { + iconPath = defaultIconPath; + } else { + iconPath = `${iconsPath}/${method.iconName}`; + } + return `
  • + + + ${
+          method.name
+        } + + ${method.name} + + + + +
  • `; +}; diff --git a/src/features/headless-checkout/web-components/payment-methods/payment-methods-attributes.enum.ts b/src/features/headless-checkout/web-components/payment-methods/payment-methods-attributes.enum.ts new file mode 100644 index 00000000..712795e6 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/payment-methods-attributes.enum.ts @@ -0,0 +1,7 @@ +export enum PaymentMethodsAttributes { + country = 'country', + skipPaymentMethodsCount = 'skipPaymentMethodsCount', + notFound = 'not-found', + searchPlaceholder = 'search-placeholder', + saveMethodMode = 'save-method-mode', +} diff --git a/src/features/headless-checkout/web-components/payment-methods/payment-methods-events.enum.ts b/src/features/headless-checkout/web-components/payment-methods/payment-methods-events.enum.ts new file mode 100644 index 00000000..76637c2f --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/payment-methods-events.enum.ts @@ -0,0 +1,3 @@ +export enum PaymentMethodsEvents { + selectionChange = 'selectionChange', +} diff --git a/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.scss b/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.scss new file mode 100644 index 00000000..27f555b1 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.scss @@ -0,0 +1,40 @@ +@use 'src/styles/mixins/typo.mixin' as typo; +@use 'src/styles/mixins/payment-methods.mixin' as payment-methods; +@use 'src/styles/mixins/controls.mixin' as controls; + +psdk-payment-methods { + @include typo.psdk-typo; + + .payment-methods { + margin: 0; + padding: 0; + } + + .search-wrapper { + display: flex; + margin-bottom: 10px; + + & .search { + @include controls.psdk-input; + + flex-grow: 1; + } + } + + .payment-method { + @include payment-methods.payment-methods; + + position: relative; + margin-bottom: 4px; + + &:last-child { + margin-bottom: 0; + } + + .arrow { + position: absolute; + right: 12px; + display: flex; + } + } +} diff --git a/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.spec.ts b/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.spec.ts new file mode 100644 index 00000000..9c7b6264 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.spec.ts @@ -0,0 +1,236 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { PaymentMethodsComponent } from './payment-methods.component'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { PaymentMethodsAttributes } from './payment-methods-attributes.enum'; +import { PaymentMethod } from '../../../../core/payment-method.interface'; +import { PaymentMethodsEvents } from './payment-methods-events.enum'; +import { HeadlessCheckout } from '../../headless-checkout'; + +function createComponent(): void { + const element = document.createElement( + WebComponentTagName.PaymentMethodsComponent, + ); + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +const mockVisiblePaymentMethod = { + id: 1, + name: '1', + isVisible: true, +} as PaymentMethod; + +const mockQiwiPaymentMethod = { + id: 16, + name: 'qiwi', + isVisible: true, +} as PaymentMethod; + +const mockNotVisiblePaymentMethod = { + id: 1, + name: '1', + isVisible: false, +} as PaymentMethod; + +describe('PaymentMethodsComponent', () => { + let headlessCheckout: HeadlessCheckout; + let headlessCheckoutSpy: HeadlessCheckoutSpy; + + window.customElements.define( + WebComponentTagName.PaymentMethodsComponent, + PaymentMethodsComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + headlessCheckout = { + getRegularMethods: noopStub, + } as unknown as HeadlessCheckout; + + headlessCheckoutSpy = { + listenAppInit: noopStub, + get appWasInit() { + return; + }, + } as unknown as HeadlessCheckoutSpy; + + container.clearInstances(); + + container + .register(HeadlessCheckoutSpy, { + useValue: headlessCheckoutSpy, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.PaymentMethodsComponent), + ).toBeDefined(); + }); + + it('Should load payment methods', () => { + const spy = spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should load payment methods after init', () => { + const spy = spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([]), + ); + const appWasInitSpy = spyOnProperty( + headlessCheckoutSpy, + 'appWasInit', + 'get', + ); + const listenAppInitSpy = spyOn(headlessCheckoutSpy, 'listenAppInit'); + listenAppInitSpy.and.callFake((callback: () => void) => { + appWasInitSpy.and.returnValue(true); + callback(); + }); + appWasInitSpy.and.returnValue(false); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should not load payment methods', () => { + const spy = spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + false, + ); + createComponent(); + expect(spy).not.toHaveBeenCalled(); + }); + + it('Should load payment methods twice if change country', () => { + const spy = spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + document + .querySelector(WebComponentTagName.PaymentMethodsComponent)! + .setAttribute(PaymentMethodsAttributes.country, 'RU'); + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('Should draw 2 payment methods', async () => { + spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([mockVisiblePaymentMethod, mockVisiblePaymentMethod]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + + // Для того, чтобы успели отрисоваться методы + await Promise.resolve(); + expect(document.querySelectorAll('.payment-method')?.length).toBe(2); + }); + + it('Should draw 2 payment methods', async () => { + spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([mockNotVisiblePaymentMethod, mockVisiblePaymentMethod]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + + await Promise.resolve(); + expect(document.querySelectorAll('.payment-method')?.length).toBe(2); + }); + + it('Should draw no payment methods', async () => { + spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([ + mockNotVisiblePaymentMethod, + mockNotVisiblePaymentMethod, + ]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + + await Promise.resolve(); + expect(document.querySelectorAll('.payment-method')?.length).toBe(2); + }); + + it('Should dispatch custom event', async () => { + spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([mockVisiblePaymentMethod]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + + await Promise.resolve(); + document + .querySelector(WebComponentTagName.PaymentMethodsComponent)! + .addEventListener(PaymentMethodsEvents.selectionChange, (event) => { + expect((event as CustomEvent).detail).toEqual({ + paymentMethodId: String(mockVisiblePaymentMethod.id), + }); + }); + + document.querySelector('a')!.click(); + }); + + it('Should search methods', async () => { + spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([mockVisiblePaymentMethod, mockQiwiPaymentMethod]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + + await Promise.resolve(); + const searchInputElement = document.querySelector('input'); + searchInputElement!.value = mockQiwiPaymentMethod.name; + const event = new Event('input', { bubbles: true }); + searchInputElement!.dispatchEvent(event); + await Promise.resolve(); + expect(document.querySelectorAll('.payment-method')?.length).toBe(1); + }); + + it('Should draw no methods', async () => { + spyOn(headlessCheckout, 'getRegularMethods').and.returnValue( + Promise.resolve([mockVisiblePaymentMethod, mockQiwiPaymentMethod]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + + await Promise.resolve(); + const searchInputElement = document.querySelector('input'); + searchInputElement!.value = '123'; + const event = new Event('input', { bubbles: true }); + searchInputElement!.dispatchEvent(event); + await Promise.resolve(); + expect(document.querySelectorAll('.payment-method')?.length).toBe(0); + }); +}); diff --git a/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.ts b/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.ts new file mode 100644 index 00000000..dc7796d4 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.ts @@ -0,0 +1,175 @@ +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { container } from 'tsyringe'; +import { PaymentMethod } from '../../../../core/payment-method.interface'; +import { getPaymentMethodTemplate } from './payment-method.template'; +import { filterPaymentMethods } from './filter-payment-methods.function'; +import { PaymentMethodsAttributes } from './payment-methods-attributes.enum'; +import { PaymentMethodsEvents } from './payment-methods-events.enum'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { PaymentMethodAttributes } from './payment-method-attributes.enum'; +import './payment-methods.component.scss'; + +export class PaymentMethodsComponent extends WebComponentAbstract { + private readonly headlessCheckout: HeadlessCheckout; + private readonly headlessCheckoutSpy: HeadlessCheckoutSpy; + private paymentMethods?: PaymentMethod[]; + private visibleMethods?: PaymentMethod[]; + private filteredMethods?: PaymentMethod[]; + + private get listRef(): HTMLElement { + return this.querySelector('ul') as HTMLElement; + } + + private get notFoundValue(): string { + return this.getAttribute(PaymentMethodsAttributes.notFound) ?? ''; + } + + private get searchPlaceHolder(): string { + return this.getAttribute(PaymentMethodsAttributes.searchPlaceholder) ?? ''; + } + + private get isSaveMethodMode(): boolean { + return !!this.getAttribute(PaymentMethodsAttributes.saveMethodMode); + } + + public constructor() { + super(); + + this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy); + this.headlessCheckout = container.resolve(HeadlessCheckout); + } + + public static get observedAttributes(): string[] { + return [ + PaymentMethodsAttributes.country, + PaymentMethodsAttributes.skipPaymentMethodsCount, + ]; + } + + protected connectedCallback(): void { + if (!this.headlessCheckoutSpy.appWasInit) { + this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); + return; + } + this.loadRegularMethods(); + } + + protected getHtml(): string { + const paymentMethodsHtml = this.getMethodsHtml(); + return ` +
    + +
    +
      + ${paymentMethodsHtml ? paymentMethodsHtml.join('') : this.notFoundValue} +
    + `; + } + + protected attributeChangedCallback(): void { + if (!this.headlessCheckoutSpy.appWasInit) { + return; + } + this.loadRegularMethods(); + } + + private loadRegularMethods(): void { + const country = + this.getAttribute(PaymentMethodsAttributes.country) ?? undefined; + + void this.headlessCheckout + .getRegularMethods({ country, isSaveMethodMode: this.isSaveMethodMode }) + .then(this.paymentMethodsLoadedHandler); + } + + private readonly paymentMethodsLoadedHandler = ( + paymentMethods: PaymentMethod[], + ): void => { + const skipPaymentMethodsCount = Number( + this.getAttribute(PaymentMethodsAttributes.skipPaymentMethodsCount), + ); + + this.paymentMethods = paymentMethods.slice(skipPaymentMethodsCount); + + this.visibleMethods = this.paymentMethods.filter( + (method) => method.isVisible, + ); + + this.filteredMethods = this.paymentMethods.slice(); + + super.render(); + this.listenClicks(); + this.setupSearch(); + }; + + private getMethodsHtml(): string[] | null { + if (this.filteredMethods?.length) { + return this.filteredMethods.map((method) => + getPaymentMethodTemplate(method), + ); + } + + return null; + } + + private listenClicks(): void { + this.addEventListenerToElement(this.listRef, 'click', (event) => { + event.preventDefault(); + if (event.target instanceof HTMLElement) { + this.dispatchSelectionEvent(event.target); + } + }); + } + + private dispatchSelectionEvent(target: HTMLElement): void { + const paymentMethodId = this.getPaymentMethodId(target); + if (!paymentMethodId) { + return; + } + + const eventOptions = { + bubbles: true, + composed: true, + detail: { + paymentMethodId: paymentMethodId, + }, + }; + + this.listRef.dispatchEvent( + new CustomEvent(PaymentMethodsEvents.selectionChange, eventOptions), + ); + } + + private getPaymentMethodId(target: HTMLElement): string | null | undefined { + return target.closest('li')?.getAttribute(PaymentMethodAttributes.methodId); + } + + private setupSearch(): void { + const searchInput = this.querySelector('.search'); + + if (searchInput) { + this.addEventListenerToElement( + searchInput as HTMLInputElement, + 'input', + (event) => { + const searchValue = (event.target as HTMLInputElement).value; + this.filteredMethods = filterPaymentMethods( + this.visibleMethods, + searchValue, + ); + this.updateMethodsView(); + }, + ); + } + } + + private updateMethodsView(): void { + const paymentMethods = this.getMethodsHtml(); + this.listRef.innerHTML = paymentMethods + ? paymentMethods.join('') + : this.notFoundValue; + } +} diff --git a/src/features/headless-checkout/web-components/payment-methods/variables.ts b/src/features/headless-checkout/web-components/payment-methods/variables.ts new file mode 100644 index 00000000..2d6bb5f3 --- /dev/null +++ b/src/features/headless-checkout/web-components/payment-methods/variables.ts @@ -0,0 +1 @@ +export const cardPid = 1380; diff --git a/src/features/headless-checkout/web-components/phone-component/phone-component-attributes.enum.ts b/src/features/headless-checkout/web-components/phone-component/phone-component-attributes.enum.ts new file mode 100644 index 00000000..1f23b8c5 --- /dev/null +++ b/src/features/headless-checkout/web-components/phone-component/phone-component-attributes.enum.ts @@ -0,0 +1,4 @@ +export enum PhoneComponentAttributes { + name = 'name', + showFlags = 'showflags', +} diff --git a/src/features/headless-checkout/web-components/phone-component/phone.component.ts b/src/features/headless-checkout/web-components/phone-component/phone.component.ts new file mode 100644 index 00000000..49f12d94 --- /dev/null +++ b/src/features/headless-checkout/web-components/phone-component/phone.component.ts @@ -0,0 +1,25 @@ +import { PhoneComponentAttributes } from './phone-component-attributes.enum'; +import { headlessCheckoutAppUrl } from '../../environment'; +import { TextComponent } from '../text-component/text.component'; +import { property, customElement } from 'lit/decorators.js'; + +@customElement('psdk-phone') +export class PhoneComponent extends TextComponent { + @property({ attribute: PhoneComponentAttributes.name }) + protected inputName = 'phone'; + + @property({ type: Boolean, attribute: PhoneComponentAttributes.showFlags }) + private readonly showFlags = false; + + protected getSecureHtml(): string { + if (!this.componentName) { + throw new Error('Component name is required'); + } + + let src = `${headlessCheckoutAppUrl}/secure-components/${this.componentName}`; + if (this.showFlags) { + src += '?showFlags=true'; + } + return ``; + } +} diff --git a/src/features/headless-checkout/web-components/qr-code/qr-code.component.spec.ts b/src/features/headless-checkout/web-components/qr-code/qr-code.component.spec.ts new file mode 100644 index 00000000..46e269a9 --- /dev/null +++ b/src/features/headless-checkout/web-components/qr-code/qr-code.component.spec.ts @@ -0,0 +1,61 @@ +import { container } from 'tsyringe'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { QrCodeComponent } from './qr-code.component'; + +class HeadlessCheckoutMock { + public events = { + onCoreEvent: noopStub, + }; +} + +function createComponent(): void { + const element = document.createElement(WebComponentTagName.QrCodeComponent); + + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +describe('QrCodeComponent', () => { + let headlessCheckout: HeadlessCheckoutMock; + let headlessCheckoutSpy: HeadlessCheckoutSpy; + + window.customElements.define( + WebComponentTagName.QrCodeComponent, + QrCodeComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + headlessCheckout = new HeadlessCheckoutMock(); + headlessCheckoutSpy = { + listenAppInit: noopStub, + get appWasInit() { + return; + }, + } as unknown as HeadlessCheckoutSpy; + + container.clearInstances(); + + container + .register(HeadlessCheckoutSpy, { + useValue: headlessCheckoutSpy, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout as unknown as HeadlessCheckout, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.QrCodeComponent), + ).not.toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/web-components/qr-code/qr-code.component.ts b/src/features/headless-checkout/web-components/qr-code/qr-code.component.ts new file mode 100644 index 00000000..abfde2aa --- /dev/null +++ b/src/features/headless-checkout/web-components/qr-code/qr-code.component.ts @@ -0,0 +1,42 @@ +import { SecureComponentAbstract } from '../../../../core/web-components/secure-component/secure-component.abstract'; +import { container } from 'tsyringe'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { EventName } from '../../../../core/event-name.enum'; +import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler'; + +export class QrCodeComponent extends SecureComponentAbstract { + protected componentName = 'qr-code'; + protected inputName = 'qr'; + private readonly headlessCheckout: HeadlessCheckout; + private readonly formSpy: FormSpy; + + public constructor() { + super(); + this.headlessCheckout = container.resolve(HeadlessCheckout); + this.formSpy = container.resolve(FormSpy); + + this.headlessCheckout.events.onCoreEvent( + EventName.finishLoadComponent, + finishLoadComponentHandler, + (res) => { + if (res?.fieldName && res?.fieldName === this.inputName) { + this.finishLoadingFormControlHandler(this.inputName); + } + }, + ); + } + + protected connectedCallback(): void { + if (!this.formSpy.formWasInit) { + this.formSpy.listenFormInit(() => this.connectedCallback()); + return; + } + + super.connectedCallback(); + } + + protected getHtml(): string { + return this.getSecureHtml(); + } +} diff --git a/src/features/headless-checkout/web-components/saved-methods/pipes/get-expire-date.pipe.ts b/src/features/headless-checkout/web-components/saved-methods/pipes/get-expire-date.pipe.ts new file mode 100644 index 00000000..9be7961f --- /dev/null +++ b/src/features/headless-checkout/web-components/saved-methods/pipes/get-expire-date.pipe.ts @@ -0,0 +1,7 @@ +export function getExpireDate(date?: { month: string; year: string }): string { + if (!date) { + return ''; + } + + return `${date.month}/${date.year.slice(2)}`; +} diff --git a/src/features/headless-checkout/web-components/saved-methods/pipes/name-cutter.pipe.ts b/src/features/headless-checkout/web-components/saved-methods/pipes/name-cutter.pipe.ts new file mode 100644 index 00000000..18c5777c --- /dev/null +++ b/src/features/headless-checkout/web-components/saved-methods/pipes/name-cutter.pipe.ts @@ -0,0 +1,41 @@ +import { SavedMethod } from '../../../../../core/saved-method.interface'; + +const replacerChar = '*'; + +function findFirstPositionOfNotReplacedChar(value: string): number { + const replacedCharLastPosition = value.split('').lastIndexOf(replacerChar); + return replacedCharLastPosition > -1 + ? replacedCharLastPosition + 1 + : replacedCharLastPosition; +} + +function getTextForCard(cardNumber: string): string { + const notReplacedCharFirstPosition = + findFirstPositionOfNotReplacedChar(cardNumber); + return notReplacedCharFirstPosition === -1 + ? `${cardNumber}` + : `•• ${cardNumber.slice(notReplacedCharFirstPosition).trim()}`; +} + +function getTextForEmail(email: string): string { + const firstPositionChar = email.indexOf('*'); + const notReplacedCharFirstPosition = + findFirstPositionOfNotReplacedChar(email); + return ( + email.slice(0, firstPositionChar).trim() + + '••' + + email.slice(notReplacedCharFirstPosition).trim() + ); +} + +export function getCutterName(method: SavedMethod): string { + if (method.type === 'card') { + return getTextForCard(method.name); + } + + if (method.name.includes('@')) { + return getTextForEmail(method.name); + } + + return method.name.replace(/\*/g, '•'); +} diff --git a/src/features/headless-checkout/web-components/saved-methods/saved-method-attributes.enum.ts b/src/features/headless-checkout/web-components/saved-methods/saved-method-attributes.enum.ts new file mode 100644 index 00000000..7f0647bc --- /dev/null +++ b/src/features/headless-checkout/web-components/saved-methods/saved-method-attributes.enum.ts @@ -0,0 +1,5 @@ +export enum SavedMethodAttributes { + paymentMethodId = 'data-payment-method-id', + savedMethodId = 'data-saved-method-id', + type = 'data-saved-method-type', +} diff --git a/src/features/headless-checkout/web-components/saved-methods/saved-method.template.spec.ts b/src/features/headless-checkout/web-components/saved-methods/saved-method.template.spec.ts new file mode 100644 index 00000000..2e073ffc --- /dev/null +++ b/src/features/headless-checkout/web-components/saved-methods/saved-method.template.spec.ts @@ -0,0 +1,16 @@ +import { getSavedMethodTemplate } from './saved-method.template'; +import { SavedMethod } from '../../../../core/saved-method.interface'; + +const mockQiwi = { + name: '4476 24** **** 9527', + psName: 'Mastercard', + iconName: 'mastercard.svg', + id: 112233, +} as SavedMethod; + +describe('getSavedMethodTemplate', () => { + it('Should draw template', () => { + expect(getSavedMethodTemplate(mockQiwi)).toContain('img'); + expect(getSavedMethodTemplate(mockQiwi)).toContain(mockQiwi.name); + }); +}); diff --git a/src/features/headless-checkout/web-components/saved-methods/saved-method.template.ts b/src/features/headless-checkout/web-components/saved-methods/saved-method.template.ts new file mode 100644 index 00000000..8af3f720 --- /dev/null +++ b/src/features/headless-checkout/web-components/saved-methods/saved-method.template.ts @@ -0,0 +1,46 @@ +import { cdnIconsUrl } from '../../environment'; +import { SavedMethod } from '../../../../core/saved-method.interface'; +import { getExpireDate } from './pipes/get-expire-date.pipe'; +import { getCutterName } from './pipes/name-cutter.pipe'; + +const iconsPath = `${cdnIconsUrl}/brand-logos`; +const defaultIconPath = `${cdnIconsUrl}/default-payment-icons/default.svg`; +const deleteIcon = `${cdnIconsUrl}/common-icons/trash-can--line.svg`; + +export const getSavedMethodTemplate = ( + method: SavedMethod, + isDeleteMode?: boolean, +): string => { + const expireDate = getExpireDate(method.cardExpiryDate); + const name = getCutterName(method); + let iconPath: string; + + if (!method.iconName) { + iconPath = defaultIconPath; + } else { + iconPath = `${iconsPath}/${method.iconName}`; + } + + return `
  • + + + ${
+          method.name
+        } + + ${name} + ${expireDate ? `${expireDate}` : ''} + ${ + isDeleteMode + ? `` + : '' + } + +
  • `; +}; diff --git a/src/features/headless-checkout/web-components/saved-methods/saved-methods-attributes.enum.ts b/src/features/headless-checkout/web-components/saved-methods/saved-methods-attributes.enum.ts new file mode 100644 index 00000000..d200b8e6 --- /dev/null +++ b/src/features/headless-checkout/web-components/saved-methods/saved-methods-attributes.enum.ts @@ -0,0 +1,4 @@ +export enum SavedMethodsAttributes { + notFound = 'not-found', + deleteMode = 'delete-mode', +} diff --git a/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.scss b/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.scss new file mode 100644 index 00000000..87ce7fa0 --- /dev/null +++ b/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.scss @@ -0,0 +1,45 @@ +@use 'src/styles/mixins/typo.mixin' as typo; +@use 'src/styles/mixins/payment-methods.mixin' as payment-methods; + +psdk-saved-methods { + @include typo.psdk-typo; + + .saved-methods { + margin: 0; + padding: 0; + } + + .saved-method { + @include payment-methods.payment-methods; + + position: relative; + margin-bottom: 4px; + + & a { + height: 50px; + padding: 12px 16px; + } + + &:last-child { + margin-bottom: 0; + } + + & .name { + flex-grow: 1; + margin-right: 8px; + } + + .arrow { + position: absolute; + right: 12px; + display: flex; + } + } + + .psdk-delete-saved-method-button { + display: flex; + border: none; + background: transparent; + cursor: pointer; + } +} diff --git a/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.spec.ts b/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.spec.ts new file mode 100644 index 00000000..dad38916 --- /dev/null +++ b/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.spec.ts @@ -0,0 +1,215 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { SavedMethodsComponent } from './saved-methods.component'; +import { SavedMethod } from '../../../../core/saved-method.interface'; +import { EventName } from '../../../../core/event-name.enum'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; + +function createComponent(isDeleteMode = false): void { + const element = document.createElement( + WebComponentTagName.SavedMethodsComponent, + ); + if (isDeleteMode) { + element.setAttribute('delete-mode', 'true'); + } + + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +const mockSavedMethod: SavedMethod = { + type: 'card', + id: 1, + currency: 'RUB', + name: '2222', + pid: 1380, + cardExpiryDate: { + month: '05', + year: '2030', + }, + recurrentType: 'charge', + form: { + paymentSid: 'id', + }, + replaced: false, + psName: 'Mastercard', + isSelected: true, + iconName: 'mastercard.svg', +}; + +describe('SavedMethodsComponent', () => { + let headlessCheckout: HeadlessCheckout; + let headlessCheckoutSpy: HeadlessCheckoutSpy; + let postMessagesClient: PostMessagesClient; + + window.customElements.define( + WebComponentTagName.SavedMethodsComponent, + SavedMethodsComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + headlessCheckout = { + getSavedMethods: noopStub, + } as unknown as HeadlessCheckout; + + headlessCheckoutSpy = { + listenAppInit: noopStub, + get appWasInit() { + return; + }, + } as unknown as HeadlessCheckoutSpy; + postMessagesClient = { + init: noopStub, + send: noopStub, + listen: noopStub, + } as unknown as PostMessagesClient; + + container.clearInstances(); + + container + .register(HeadlessCheckoutSpy, { + useValue: headlessCheckoutSpy, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }) + .register(Window, { useValue: window }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.SavedMethodsComponent), + ).toBeDefined(); + }); + + it('Should load saved methods', () => { + const spy = spyOn(headlessCheckout, 'getSavedMethods').and.returnValue( + Promise.resolve([]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should load saved methods after init', () => { + const spy = spyOn(headlessCheckout, 'getSavedMethods').and.returnValue( + Promise.resolve([]), + ); + const appWasInitSpy = spyOnProperty( + headlessCheckoutSpy, + 'appWasInit', + 'get', + ); + const listenAppInitSpy = spyOn(headlessCheckoutSpy, 'listenAppInit'); + listenAppInitSpy.and.callFake((callback: () => void) => { + appWasInitSpy.and.returnValue(true); + callback(); + }); + appWasInitSpy.and.returnValue(false); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should not load saved methods', () => { + const spy = spyOn(headlessCheckout, 'getSavedMethods').and.returnValue( + Promise.resolve([]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + false, + ); + createComponent(); + expect(spy).not.toHaveBeenCalled(); + }); + + it('Should draw 2 payment methods', async () => { + spyOn(headlessCheckout, 'getSavedMethods').and.returnValue( + Promise.resolve([mockSavedMethod, mockSavedMethod]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + + // Для того, чтобы успели отрисоваться методы + await Promise.resolve(); + expect(document.querySelectorAll('.saved-method')?.length).toBe(2); + }); + + it('Should draw no saved methods', async () => { + spyOn(headlessCheckout, 'getSavedMethods').and.returnValue( + Promise.resolve([]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + + await Promise.resolve(); + expect(document.querySelectorAll('.saved-method')?.length).toBe(0); + }); + + it('Should dispatch custom event', async () => { + spyOn(headlessCheckout, 'getSavedMethods').and.returnValue( + Promise.resolve([mockSavedMethod]), + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + createComponent(); + + await Promise.resolve(); + document + .querySelector(WebComponentTagName.SavedMethodsComponent)! + .addEventListener(EventName.savedMethodSelected, (event) => { + expect((event as CustomEvent).detail).toEqual({ + paymentMethodId: String(mockSavedMethod.pid), + savedMethodId: String(mockSavedMethod.id), + type: String(mockSavedMethod.type), + }); + }); + + document.querySelector('a')!.click(); + }); + + it('Should dispatch custom delete event', async () => { + spyOn(postMessagesClient, 'send').and.returnValue(Promise.resolve(true)); + + spyOn(headlessCheckout, 'getSavedMethods').and.returnValue( + Promise.resolve([mockSavedMethod]), + ); + + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true, + ); + + createComponent(true); + + await Promise.resolve(); + document + .querySelector(WebComponentTagName.SavedMethodsComponent)! + .addEventListener(EventName.deleteSavedMethod, (event) => { + expect((event as CustomEvent).detail).toEqual({ + paymentMethodId: String(mockSavedMethod.pid), + savedMethodId: String(mockSavedMethod.id), + type: String(mockSavedMethod.type), + }); + }); + + document.querySelector('button')!.click(); + }); +}); diff --git a/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.ts b/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.ts new file mode 100644 index 00000000..c76bdd76 --- /dev/null +++ b/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.ts @@ -0,0 +1,228 @@ +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { container } from 'tsyringe'; +import { SavedMethod } from '../../../../core/saved-method.interface'; +import { getSavedMethodTemplate } from './saved-method.template'; +import { SavedMethodsAttributes } from './saved-methods-attributes.enum'; +import { SavedMethodAttributes } from './saved-method-attributes.enum'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { deleteSavedMethodHandler } from '../../post-messages-handlers/delete-saved-method.handler'; +import { EventName } from '../../../../core/event-name.enum'; +import './saved-methods.component.scss'; +import { cdnIconsUrl } from '../../environment'; + +export class SavedMethodsComponent extends WebComponentAbstract { + private readonly headlessCheckout: HeadlessCheckout; + private readonly headlessCheckoutSpy: HeadlessCheckoutSpy; + private readonly window: Window; + private readonly postMessagesClient: PostMessagesClient; + private readonly deleteIcon = `${cdnIconsUrl}/common-icons/trash-can--line.svg`; + + private savedMethods?: SavedMethod[]; + + private get listRef(): HTMLElement { + return this.querySelector('ul') as HTMLElement; + } + + private get notFoundValue(): string { + return this.getAttribute(SavedMethodsAttributes.notFound) ?? ''; + } + + private get deleteMode(): string { + return this.getAttribute(SavedMethodsAttributes.deleteMode) ?? ''; + } + + public constructor() { + super(); + + this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy); + this.headlessCheckout = container.resolve(HeadlessCheckout); + this.window = container.resolve(Window); + this.postMessagesClient = container.resolve(PostMessagesClient); + } + + public static get observedAttributes(): string[] { + return [SavedMethodsAttributes.deleteMode]; + } + + protected attributeChangedCallback( + name: string, + oldValue: string, + newValue: string, + ): void { + if ( + !this.headlessCheckoutSpy.appWasInit || + name !== SavedMethodsAttributes.deleteMode + ) { + return; + } + if (this.isDeleteMode(newValue)) { + const savedMethodLinks = this.querySelectorAll('.saved-method a'); + + savedMethodLinks.forEach((el) => { + el.appendChild(this.createDeleteButton()); + }); + + return; + } + + this.removeAllDeleteButton(); + } + + protected connectedCallback(): void { + if (!this.headlessCheckoutSpy.appWasInit) { + this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); + return; + } + this.loadSavedMethods(); + } + + protected getHtml(): string { + const savedMethodsHtml = this.getMethodsHtml(); + return ` +
      + ${savedMethodsHtml ? savedMethodsHtml.join('') : this.notFoundValue} +
    + `; + } + + private loadSavedMethods(): void { + void this.headlessCheckout + .getSavedMethods() + .then(this.savedMethodsLoadedHandler); + } + + private readonly savedMethodsLoadedHandler = ( + savedMethods: SavedMethod[], + ): void => { + this.savedMethods = savedMethods; + super.render(); + this.listenClicks(); + }; + + private getMethodsHtml(): string[] | null { + if (this.savedMethods?.length) { + return this.savedMethods.map((method) => + getSavedMethodTemplate(method, this.isDeleteMode(this.deleteMode)), + ); + } + + return null; + } + + private listenClicks(): void { + this.addEventListenerToElement(this.listRef, 'click', (event) => { + event.preventDefault(); + + if (event.target instanceof HTMLButtonElement) { + this.deleteButtonClick(event.target); + return; + } + + if ( + event.target instanceof HTMLElement && + !this.isDeleteMode(this.deleteMode) + ) { + this.dispatchSelectionEvent(event.target); + } + }); + } + + private dispatchSelectionEvent(target: HTMLElement): void { + if (!this.getSavedMethodData(target).savedMethodId) { + return; + } + + const eventOptions = { + bubbles: true, + composed: true, + detail: { + ...this.getSavedMethodData(target), + }, + }; + + this.listRef.dispatchEvent( + new CustomEvent(EventName.savedMethodSelected, eventOptions), + ); + } + + private dispatchDeleteMethodStatus(isDeleteSuccessful: boolean): void { + const eventOptions = { + bubbles: true, + composed: true, + detail: { + isDeleteSuccessful, + }, + }; + + this.listRef.dispatchEvent( + new CustomEvent(EventName.deletedSavedMethodStatus, eventOptions), + ); + } + + private getSavedMethodData(target: HTMLElement): { + paymentMethodId: string | null | undefined; + savedMethodId: string | null | undefined; + type: string | null | undefined; + } { + return { + paymentMethodId: target + .closest('li') + ?.getAttribute(SavedMethodAttributes.paymentMethodId), + savedMethodId: target + .closest('li') + ?.getAttribute(SavedMethodAttributes.savedMethodId), + type: target.closest('li')?.getAttribute(SavedMethodAttributes.type), + }; + } + + private removeAllDeleteButton(): void { + const buttons = this.querySelectorAll('.psdk-delete-saved-method-button'); + + for (const button of buttons) { + button.remove(); + } + } + + private deleteButtonClick(target: HTMLButtonElement): void { + const savedMethodData = this.getSavedMethodData(target); + void this.postMessagesClient + .send( + { + name: EventName.deleteSavedMethod, + data: { ...savedMethodData }, + }, + deleteSavedMethodHandler, + ) + .then((isDeleteSuccessful) => { + this.dispatchDeleteMethodStatus(isDeleteSuccessful as boolean); + + if (isDeleteSuccessful) { + this.removeSavedMethodFromList(savedMethodData.savedMethodId!); + } + }); + } + + private removeSavedMethodFromList(savedMethodId: string): void { + this.querySelector( + `li[${SavedMethodAttributes.savedMethodId}="${savedMethodId}"]`, + )?.remove(); + } + + private createDeleteButton(): HTMLButtonElement { + const button = this.window.document.createElement('button'); + button.className = 'psdk-delete-saved-method-button'; + + const deleteIconElement = this.window.document.createElement('img'); + deleteIconElement.src = this.deleteIcon; + button.appendChild(deleteIconElement); + button.style.cursor = 'pointer'; + + return button; + } + + private isDeleteMode(attributeValue: string): boolean { + return attributeValue === 'true'; + } +} diff --git a/src/features/headless-checkout/web-components/select/select-attributes.enum.ts b/src/features/headless-checkout/web-components/select/select-attributes.enum.ts new file mode 100644 index 00000000..bb183f55 --- /dev/null +++ b/src/features/headless-checkout/web-components/select/select-attributes.enum.ts @@ -0,0 +1,4 @@ +export enum SelectAttributes { + name = 'name', + type = 'type', +} diff --git a/src/features/headless-checkout/web-components/select/select-component.config.interface.ts b/src/features/headless-checkout/web-components/select/select-component.config.interface.ts new file mode 100644 index 00000000..590f2620 --- /dev/null +++ b/src/features/headless-checkout/web-components/select/select-component.config.interface.ts @@ -0,0 +1,9 @@ +import { ControlComponentConfig } from '../control-component-config.interface'; +import { TextConfigTooltip } from '../text-component/text-config-tooltip.interface'; + +export interface SelectComponentConfig extends ControlComponentConfig { + readonly?: boolean; + options?: Array<{ label: string; value: string }>; + tooltip?: TextConfigTooltip; + initValue?: string; +} diff --git a/src/features/headless-checkout/web-components/select/select-keys.enum.ts b/src/features/headless-checkout/web-components/select/select-keys.enum.ts new file mode 100644 index 00000000..7d6c4326 --- /dev/null +++ b/src/features/headless-checkout/web-components/select/select-keys.enum.ts @@ -0,0 +1,6 @@ +export enum SelectKeys { + enter = 'enter', + space = 'space', + arrowUp = 'arrowup', + arrowDown = 'arrowdown', +} diff --git a/src/features/headless-checkout/web-components/select/select-type.enum.ts b/src/features/headless-checkout/web-components/select/select-type.enum.ts new file mode 100644 index 00000000..a2a2c20a --- /dev/null +++ b/src/features/headless-checkout/web-components/select/select-type.enum.ts @@ -0,0 +1,4 @@ +export enum SelectType { + country = 'country', + formControl = 'form-control', +} diff --git a/src/features/headless-checkout/web-components/select/select.component.scss b/src/features/headless-checkout/web-components/select/select.component.scss new file mode 100644 index 00000000..5a345e7b --- /dev/null +++ b/src/features/headless-checkout/web-components/select/select.component.scss @@ -0,0 +1,93 @@ +@use 'src/styles/mixins/controls.mixin' as controls; + +psdk-select { + .select { + @include controls.psdk-select; + + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + cursor: pointer; + } + + .arrow { + display: flex; + + &.arrow-up { + transform: rotate(180deg); + } + } + + .select-options { + width: 100%; + height: 100%; + max-height: 200px; + } + + .dropdown { + position: absolute; + z-index: 1; + display: none; + flex-direction: column; + align-items: center; + overflow-x: hidden; + overflow-y: auto; + width: 100%; + height: 216px; + padding: 8px 0; + border-radius: 8px; + background: var(--psdk-bg-secondary); + box-shadow: var(--psdk-shadow); + + /* Hide scrollbar for IE, Edge and Firefox */ + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + border: 1px solid transparent; + border-radius: 4px; + background: var(--psdk-divider-color); + } + + ::-webkit-scrollbar-track { + border: 4px solid transparent; + border-radius: 1px; + background: var(--psdk-divider-color); + } + } + + .dropdown-wrapper { + position: relative; + + &.dropdown-opened { + & .dropdown { + display: flex; + } + } + } + + .option { + display: flex; + + :hover, &.active { + background: var(--psdk-select-hover-bg); + } + + &.focused { + background: var(--psdk-select-focus-bg); + color: var(--psdk-select-focus-color); + } + + .option-content { + width: 100%; + height: 100%; + padding: 11px 12px; + cursor: pointer; + } + } +} diff --git a/src/features/headless-checkout/web-components/select/select.component.spec.ts b/src/features/headless-checkout/web-components/select/select.component.spec.ts new file mode 100644 index 00000000..27898c76 --- /dev/null +++ b/src/features/headless-checkout/web-components/select/select.component.spec.ts @@ -0,0 +1,163 @@ +import { container } from 'tsyringe'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { SelectAttributes } from './select-attributes.enum'; +import { SelectComponent } from './select.component'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { FormLoader } from '../../../../core/form/form-loader'; + +function createComponent(name: string): HTMLElement { + const element = document.createElement(WebComponentTagName.SelectComponent); + + element.setAttribute(SelectAttributes.name, name); + element.setAttribute('id', 'test'); + + (document.getElementById('container')! as HTMLElement).appendChild(element); + + return element; +} + +const configMock = { + name: 'state', + options: [ + { + value: 'first', + label: 'first-label', + }, + { + value: 'second', + label: 'second-label', + }, + ], +}; + +describe('SelectComponent', () => { + let postMessagesClient: PostMessagesClient; + let headlessCheckout: HeadlessCheckout; + let headlessCheckoutSpy: HeadlessCheckoutSpy; + let formLoader: FormLoader; + + window.customElements.define( + WebComponentTagName.SelectComponent, + SelectComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + postMessagesClient = { + send: noopStub, + } as unknown as PostMessagesClient; + + formLoader = { + setupAndAwaitFieldsLoading: noopStub, + setFieldLoaded: noopStub, + } as unknown as FormLoader; + + headlessCheckout = { + form: { + onFieldsStatusChange: noopStub, + }, + } as unknown as HeadlessCheckout; + + headlessCheckoutSpy = { + listenAppInit: noopStub, + get appWasInit() { + return true; + }, + } as unknown as HeadlessCheckoutSpy; + + container.clearInstances(); + + container + .register(PostMessagesClient, { + useValue: postMessagesClient, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }) + .register(FormSpy, { + useValue: { + get formWasInit() { + return true; + }, + } as FormSpy, + }) + .register(Window, { + useValue: window, + }) + .register(HeadlessCheckoutSpy, { + useValue: headlessCheckoutSpy, + }) + .register(FormLoader, { + useValue: formLoader, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + const appWasInitSpy = spyOnProperty( + headlessCheckoutSpy, + 'appWasInit', + 'get', + ); + const listenAppInitSpy = spyOn(headlessCheckoutSpy, 'listenAppInit'); + listenAppInitSpy.and.callFake((callback: () => void) => { + appWasInitSpy.and.returnValue(true); + callback(); + }); + }); + + it('Should create component', () => { + spyOn(postMessagesClient, 'send').and.resolveTo(configMock); + + createComponent('state'); + + expect( + document.querySelector(WebComponentTagName.SelectComponent), + ).toBeDefined(); + }); + + it('should render select with default option', (doneFn) => { + const postMessageSpy = spyOn(postMessagesClient, 'send').and.resolveTo( + configMock, + ); + + const selectComponent = createComponent('state'); + + setTimeout(() => { + const selectContentElement = + selectComponent.querySelector('#select-content'); + + expect(postMessageSpy).toHaveBeenCalled(); + expect(selectContentElement?.textContent?.replace(/\s/g, '')).toEqual( + configMock.options[0].label, + ); + + doneFn(); + }, 0); + }); + + it('should render all options in dropdown', (doneFn) => { + const postMessageSpy = spyOn(postMessagesClient, 'send').and.resolveTo( + configMock, + ); + + const selectComponent = createComponent('state'); + + setTimeout(() => { + const selectOptionsElement = + selectComponent.querySelector('#select-options'); + + expect(postMessageSpy).toHaveBeenCalled(); + expect(selectOptionsElement?.children.length).toEqual( + configMock.options.length, + ); + + doneFn(); + }, 0); + }); +}); diff --git a/src/features/headless-checkout/web-components/select/select.component.ts b/src/features/headless-checkout/web-components/select/select.component.ts new file mode 100644 index 00000000..18ea3a24 --- /dev/null +++ b/src/features/headless-checkout/web-components/select/select.component.ts @@ -0,0 +1,429 @@ +import { BaseControl } from '../base-control/base-control.abstract'; +import { + getSelectComponentTemplate, + getSelectOptionTemplate, +} from './select.template'; +import { SelectAttributes } from './select-attributes.enum'; +import { SelectComponentConfig } from './select-component.config.interface'; +import './select.component.scss'; +import { SelectKeys } from './select-keys.enum'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { container } from 'tsyringe'; +import { EventName } from '../../../../core/event-name.enum'; +import { SelectType } from './select-type.enum'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; + +export class SelectComponent extends BaseControl { + protected config: SelectComponentConfig | null = null; + + private selectedOptionValue = ''; + private isOpened = false; + private selectedOptionIndex!: number; + private currentHoverOptionIndex!: number; + private readonly headlessCheckoutSpy!: HeadlessCheckoutSpy; + private readonly formSpy!: FormSpy; + + private get rootElement(): this | ShadowRoot { + return this.shadowRoot ?? this; + } + + public get options(): HTMLElement | null { + return this.rootElement.querySelector('#select-options'); + } + + public constructor() { + super(); + this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy); + this.formSpy = container.resolve(FormSpy); + } + + public get nameAttr(): string { + return this.getAttribute(SelectAttributes.name) ?? ''; + } + + public get typeAttr(): string { + return this.getAttribute(SelectAttributes.type) ?? ''; + } + + public static get observedAttributes(): string[] { + return [SelectAttributes.name, SelectAttributes.name]; + } + + protected connectedCallback(): void { + this.controlName = this.nameAttr || this.typeAttr; + + if (!this.controlName) { + return; + } + + if (!this.formSpy.formWasInit && this.typeAttr !== SelectType.country) { + this.formSpy.listenFormInit(() => this.connectedCallback()); + return; + } + + if ( + !this.headlessCheckoutSpy.appWasInit && + this.typeAttr === SelectType.country + ) { + this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); + return; + } + + this.loadConfig(); + } + + protected attributeChangedCallback(): void { + if (!this.controlName) { + return this.connectedCallback(); + } + + super.attributeChangedCallback(); + } + + protected getHtml(): string { + if (!this.config) { + return ''; + } + + return getSelectComponentTemplate(this.config); + } + + protected render(): void { + super.render(); + + this.addOptions(); + + this.addHandlers(); + } + + protected notifyOnFocusEvent(): void { + if (this.typeAttr === SelectType.country) { + return; + } + + super.notifyOnFocusEvent(); + } + + protected notifyOnBlurEvent(): void { + if (this.typeAttr === SelectType.country) { + return; + } + + super.notifyOnBlurEvent(); + } + + protected notifyOnValueChanges(value: unknown): void { + if (this.typeAttr === SelectType.country) { + this.dispatchChangeCountryEvent(value); + + return; + } + super.notifyOnValueChanges(value); + } + + private dispatchChangeCountryEvent(value: unknown): void { + const eventOptions = { + bubbles: true, + composed: true, + detail: { + country: value, + }, + }; + + this.rootElement.dispatchEvent( + new CustomEvent(EventName.userCountryChanged, eventOptions), + ); + } + + private toggleDropdown(): void { + this.isOpened = !this.isOpened; + + const dropdownWrapper = this.rootElement.querySelector('#dropdown-wrapper'); + const dropdownIcon = this.rootElement.querySelector('#dropdown-icon'); + + if (!dropdownWrapper || !dropdownIcon) { + return; + } + + dropdownWrapper.classList.toggle('dropdown-opened', this.isOpened); + dropdownIcon.classList.toggle('arrow-down', !this.isOpened); + dropdownIcon.classList.toggle('arrow-up', this.isOpened); + } + + private closeDropdown(): void { + this.isOpened = false; + + const dropdownWrapper = this.rootElement.querySelector('#dropdown-wrapper'); + const dropdownIcon = this.rootElement.querySelector('#dropdown-icon'); + + if (dropdownWrapper) { + dropdownWrapper.classList.remove('dropdown-opened'); + } + + if (dropdownIcon) { + dropdownIcon.classList.remove('arrow-up'); + dropdownIcon.classList.add('arrow-down'); + } + } + + private addHandlers(): void { + this.removeAllEventListeners(); + + const button = this.rootElement.querySelector('#select-button'); + + if (!button || !this.options) { + return; + } + + this.addEventListenerToElement(button, 'click', () => this.onButtonClick()); + + this.addEventListenerToElement(button, 'blur', () => this.onButtonBlur()); + + this.addEventListenerToElement(this.options, 'click', (event) => + this.onSelectOption(event), + ); + + this.addEventListenerToElement(button, 'keydown', (event) => { + const key = (event as KeyboardEvent).key.toLowerCase(); + const code = (event as KeyboardEvent).code.toLowerCase(); + + const optionsElements = Array.from( + this.options!.children, + ) as HTMLElement[]; + + if (/[a-z]/i.test(key) && key.length === 1) { + this.handleLetterKey(key, optionsElements); + } else if (key === SelectKeys.enter || code === SelectKeys.space) { + this.handleEnterOrSpaceKey(event as KeyboardEvent); + } else if ( + (key === SelectKeys.arrowUp || key === SelectKeys.arrowDown) && + this.isOpened + ) { + this.handleArrowKey(key); + } + }); + + // hack to store global event + this.addEventListenerToElement( + this.window as unknown as Element, + 'click', + (event) => this.onClickOutside(event), + ); + } + + private addOptions(): void { + if (this.options && this.config?.options) { + let optionsTemplate = ''; + + this.selectedOptionIndex = this.config.options.findIndex( + (option) => option.value === this.config?.initValue, + ); + this.currentHoverOptionIndex = 0; + + this.config.options.forEach((option, index) => { + optionsTemplate += getSelectOptionTemplate(option, index); + }); + + this.options.innerHTML = optionsTemplate; + const focusedOption = this.options.children[ + this.selectedOptionIndex + ] as HTMLElement; + + if (focusedOption) { + this.setFocusedClass(focusedOption); + } + } + } + + private onButtonClick(): void { + this.toggleDropdown(); + + if (this.isOpened) { + this.notifyOnFocusEvent(); + } + } + + private onButtonBlur(): void { + this.notifyOnBlurEvent(); + } + + private onClickOutside(event: Event): void { + if (!event.target) { + return; + } + + if ( + this.isOpened && + !this.rootElement.contains(event.target as HTMLElement) + ) { + this.closeDropdown(); + } + } + + private handleLetterKey(key: string, optionsElements: HTMLElement[]): void { + const activeElements = this.options?.querySelectorAll('.active'); + activeElements?.forEach((element) => + this.removeActiveClass(element as HTMLElement), + ); + + for (let index = 0; index < optionsElements.length; index++) { + if ( + optionsElements[index].getAttribute('data-option-key-letter') === key + ) { + this.currentHoverOptionIndex = index; + this.setActiveClass(optionsElements[index]); + optionsElements[index].focus(); + optionsElements[index].scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); + break; + } + } + } + + private handleEnterOrSpaceKey(event: KeyboardEvent): void { + const option = this.options?.querySelector('.active') as HTMLElement | null; + if (!option) { + return; + } + + const clickEvent = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + }); + + option.children[0].dispatchEvent(clickEvent); + event.preventDefault(); + } + + private handleArrowKey(key: string): void { + const activeElements = this.options?.querySelectorAll('.active'); + activeElements?.forEach((element) => + this.removeActiveClass(element as HTMLElement), + ); + + if (key === SelectKeys.arrowUp && this.currentHoverOptionIndex !== 0) { + this.currentHoverOptionIndex--; + } + + if ( + this.config?.options?.length && + key === SelectKeys.arrowDown && + this.currentHoverOptionIndex !== this.config?.options?.length - 1 + ) { + this.currentHoverOptionIndex++; + } + + const option = this.options?.querySelector( + `[data-option-index='${this.currentHoverOptionIndex}']`, + ); + + if (!option) { + return; + } + + this.setActiveClass(option as HTMLElement); + + option.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); + } + + private onSelectOption(event: Event): void { + const eventTarget = event.target as HTMLElement; + + if (!eventTarget) return; + + const optionValue = this.getOptionValue(eventTarget); + const optionIndex = this.getOptionIndex(eventTarget); + const previousOption = this.getOptionByIndex(this.selectedOptionIndex); + + if (!optionValue || !optionIndex || !previousOption) return; + + this.removeFocusedClass(previousOption as HTMLElement); + + this.selectedOptionValue = optionValue; + this.selectedOptionIndex = this.currentHoverOptionIndex = + Number(optionIndex); + + const selectedOptionElement = + this.rootElement.querySelector('#select-content'); + const selectedOption = this.config?.options?.find( + (option) => this.selectedOptionValue === option.value, + ); + + if (!selectedOptionElement || !eventTarget.parentElement) return; + + selectedOptionElement.innerHTML = selectedOption?.label ?? ''; + this.setFocusedClass(eventTarget.parentElement); + + this.closeDropdown(); + this.notifyOnValueChanges(this.selectedOptionValue); + } + + private getOptionValue(target: HTMLElement): string | null { + return target.getAttribute('data-option-value'); + } + + private getOptionIndex(target: HTMLElement): string | null { + return target.parentElement?.getAttribute('data-option-index') ?? null; + } + + private getOptionByIndex(index: number): Element | null { + return ( + this.options?.querySelector(`[data-option-index='${index}']`) ?? null + ); + } + + private setActiveClass(element: HTMLElement): void { + element.classList.add('active'); + } + + private removeActiveClass(element: HTMLElement): void { + element.classList.remove('active'); + } + + private setFocusedClass(element: HTMLElement): void { + element.classList.add('focused'); + } + + private removeFocusedClass(element: HTMLElement): void { + element.classList.remove('focused'); + } + + private setupSelectCountryConfig(): void { + void this.headlessCheckout.getCountryList().then((data) => { + this.config = { + options: data.countryList.map((country) => ({ + label: country.name, + value: country.ISO, + })), + }; + + this.config.initValue = data.currentCountry; + + this.render(); + }); + } + + private loadConfig(): void { + switch (this.typeAttr) { + case SelectType.country: { + this.setupSelectCountryConfig(); + + break; + } + default: + this.listenFieldStatusChange(); + + void this.getComponentConfig(this.controlName).then((config) => { + this.config = config; + this.render(); + this.finishLoadingFormControlHandler(this.controlName); + }); + } + } +} diff --git a/src/features/headless-checkout/web-components/select/select.template.spec.ts b/src/features/headless-checkout/web-components/select/select.template.spec.ts new file mode 100644 index 00000000..d8ad9d18 --- /dev/null +++ b/src/features/headless-checkout/web-components/select/select.template.spec.ts @@ -0,0 +1,26 @@ +import { getSelectComponentTemplate } from './select.template'; + +describe('getSelectComponentTemplate', () => { + it('should draw all blocks', () => { + const configMock = { + title: 'title', + options: [ + { + value: 'first', + label: 'first-label', + }, + { + value: 'second', + label: 'second-label', + }, + ], + error: 'error message', + }; + + const selectTemplate = getSelectComponentTemplate(configMock); + + expect(selectTemplate).toContain('title'); + expect(selectTemplate).toContain('first-label'); + expect(selectTemplate).toContain('error message'); + }); +}); diff --git a/src/features/headless-checkout/web-components/select/select.template.ts b/src/features/headless-checkout/web-components/select/select.template.ts new file mode 100644 index 00000000..fdb4e9f9 --- /dev/null +++ b/src/features/headless-checkout/web-components/select/select.template.ts @@ -0,0 +1,49 @@ +import { SelectComponentConfig } from './select-component.config.interface'; +import { cdnIconsUrl } from '../../environment'; + +const arrow = `${cdnIconsUrl}/common-icons/chevron-down--line.svg`; +export const getSelectOptionTemplate = ( + option: { + label: string; + value: string; + }, + index: number, +): string => { + return ` + +
    + ${option.label} +
    +
    + `; +}; + +export const getSelectComponentTemplate = ( + config: SelectComponentConfig, +): string => { + return ` +
    + ${config.title ? `` : ''} + + + + + ${config.error ? `
    ${config.error}
    ` : ''} +
    + `; +}; diff --git a/src/features/headless-checkout/web-components/status/status.component.scss b/src/features/headless-checkout/web-components/status/status.component.scss new file mode 100644 index 00000000..26543b6d --- /dev/null +++ b/src/features/headless-checkout/web-components/status/status.component.scss @@ -0,0 +1,43 @@ +@use 'src/styles/mixins/typo.mixin' as typo; +@use 'src/styles/mixins/animation.mixin' as animation; + +psdk-status { + @include typo.psdk-typo; + + .status { + & > * { + margin-bottom: 4px; + } + + & > *:last-child { + margin-bottom: 0; + } + } + + .title-text { + @include typo.psdk-title2; + + margin: 0; + padding: 0; + font-weight: 500; + } + + .description { + margin-top: 0; + } + + .loader { + @include animation.loader(var(--psdk-loader-color), 24px, 3px); + + margin-top: 20px; + } + + .message { + @include typo.psdk-typo; + + padding: 12px; + border-radius: var(--psdk-common-border-radius); + background-color: var(--psdk-neutral-bg); + color: var(--psdk-neutral-color); + } +} diff --git a/src/features/headless-checkout/web-components/status/status.component.spec.ts b/src/features/headless-checkout/web-components/status/status.component.spec.ts new file mode 100644 index 00000000..a35f1bbf --- /dev/null +++ b/src/features/headless-checkout/web-components/status/status.component.spec.ts @@ -0,0 +1,94 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { HeadlessCheckoutMock } from '../../../../tests/stubs/headless-checkout.mock'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { StatusComponent } from './status.component'; +import { Status } from '../../../../core/status/status.interface'; + +function createComponent(): void { + const element = document.createElement(WebComponentTagName.StatusComponent); + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +describe('StatusComponent', () => { + let headlessCheckout: HeadlessCheckoutMock; + let headlessCheckoutSpy: HeadlessCheckoutSpy; + + window.customElements.define( + WebComponentTagName.StatusComponent, + StatusComponent + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + headlessCheckout = new HeadlessCheckoutMock(); + headlessCheckoutSpy = { + listenAppInit: noopStub, + get appWasInit() { + return; + }, + } as unknown as HeadlessCheckoutSpy; + + container.clearInstances(); + + container + .register(HeadlessCheckoutSpy, { + useValue: headlessCheckoutSpy, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout as unknown as HeadlessCheckout, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.StatusComponent) + ).toBeDefined(); + }); + + it('Should get status', () => { + const spy = spyOn(headlessCheckout, 'getStatus'); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should not draw for empty form messages event', () => { + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + headlessCheckout.emitEvent(null); + const element = document.querySelector(WebComponentTagName.StatusComponent); + expect(element!.innerHTML).toEqual(''); + }); + + it('Should draw status component', async () => { + const mockStatus: Status = { + statusState: 'done', + } as unknown as Status; + spyOn(headlessCheckout, 'getStatus').and.returnValue( + Promise.resolve(mockStatus) + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + // delay the template to be drawn + await Promise.resolve(); + + const element = document.querySelector(WebComponentTagName.StatusComponent); + expect(element!.innerHTML).not.toEqual(''); + }); +}); diff --git a/src/features/headless-checkout/web-components/status/status.component.template.spec.ts b/src/features/headless-checkout/web-components/status/status.component.template.spec.ts new file mode 100644 index 00000000..5d892f82 --- /dev/null +++ b/src/features/headless-checkout/web-components/status/status.component.template.spec.ts @@ -0,0 +1,35 @@ +import { getStatusComponentTemplate } from './status.component.template'; +import { StatusComponentConfig } from './template-config/status.component.config.interface'; +import { StatusEnum } from '../../../../core/status/status.enum'; + +const mockStatusConfig: StatusComponentConfig = { + title: 'title', + image: 'mock-image', + description: '', + showDescription: false, + status: StatusEnum.done, + autoCancellation: true, +}; +const mockStatusConfigWithDescription: StatusComponentConfig = { + title: 'title', + image: 'mock-image', + description: 'description', + showDescription: true, + status: StatusEnum.done, + autoCancellation: true, +}; + +describe('getStatusComponentTemplate', () => { + it('Should not contains description', () => { + const template = getStatusComponentTemplate(mockStatusConfig); + expect(template).toContain('title'); + expect(template).not.toContain('description'); + }); + + it('Should contains description', () => { + const template = getStatusComponentTemplate( + mockStatusConfigWithDescription, + ); + expect(template).toContain('description'); + }); +}); diff --git a/src/features/headless-checkout/web-components/status/status.component.template.ts b/src/features/headless-checkout/web-components/status/status.component.template.ts new file mode 100644 index 00000000..ccddb241 --- /dev/null +++ b/src/features/headless-checkout/web-components/status/status.component.template.ts @@ -0,0 +1,33 @@ +import { StatusComponentConfig } from './template-config/status.component.config.interface'; +import i18next from 'i18next'; + +export const getStatusComponentTemplate = ( + statusConfig: StatusComponentConfig, +): string => { + const autoCancellationMessage = !statusConfig.autoCancellation + ? `
    ${i18next.t( + 'status.message.auto-cancellation', + )}
    ` + : ''; + + return ` +
    +
    +

    ${statusConfig.title}

    +
    + + + + ${ + statusConfig.showDescription + ? `

    ${statusConfig.description}

    ` + : '' + } + ${ + statusConfig.status === 'processing' || statusConfig.status === 'awaiting' + ? `${autoCancellationMessage}
    ` + : '' + } +
    + `; +}; diff --git a/src/features/headless-checkout/web-components/status/status.component.ts b/src/features/headless-checkout/web-components/status/status.component.ts new file mode 100644 index 00000000..d8944029 --- /dev/null +++ b/src/features/headless-checkout/web-components/status/status.component.ts @@ -0,0 +1,89 @@ +import { container } from 'tsyringe'; +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { isStatusUpdatedAction } from '../../../../core/actions/is-status-updated-action.function'; +import { StatusUpdatedAction } from '../../../../core/actions/status-updated.action.type'; +import { Status } from '../../../../core/status/status.interface'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { getStatusComponentTemplate } from './status.component.template'; +import { StatusComponentConfig } from './template-config/status.component.config.interface'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { getPaymentStatusConfig } from './template-config/get-payment-status-config.function'; +import { getSavingMethodStatusConfig } from './template-config/get-saving-method-status-config.function'; +import { getStatusState } from './template-config/get-status-state.function'; +import './status.component.scss'; + +export class StatusComponent extends WebComponentAbstract { + private readonly headlessCheckout: HeadlessCheckout; + private readonly headlessCheckoutSpy: HeadlessCheckoutSpy; + private statusConfig: StatusComponentConfig | null = null; + private prevStatusUpdate: StatusUpdatedAction | null = null; + + public constructor() { + super(); + + this.headlessCheckout = container.resolve(HeadlessCheckout); + this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy); + } + + protected connectedCallback(): void { + if (!this.headlessCheckoutSpy.appWasInit) { + this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); + return; + } + void this.getStatus(); + this.listenFormInit(); + } + + protected getHtml(): string { + if (this.statusConfig) { + return getStatusComponentTemplate(this.statusConfig); + } + + return ''; + } + + private listenFormInit(): void { + this.headlessCheckout.form.onNextAction((nextAction) => { + if ( + isStatusUpdatedAction(nextAction) && + nextAction.data.statusState !== this.prevStatusUpdate?.data?.statusState + ) { + this.prevStatusUpdate = nextAction; + + void this.getStatus(); + } + }); + } + + private statusLoadedHandler( + statusConfig: StatusComponentConfig | null, + ): void { + this.statusConfig = statusConfig; + + this.render(); + } + + private async getStatus(): Promise { + const status = await this.headlessCheckout.getStatus(); + + const statusConfig = this.getStatusConfig(status); + this.statusLoadedHandler(statusConfig); + } + + private getStatusConfig(status: Status | null): StatusComponentConfig | null { + if (!status) { + return null; + } + + const statusState = getStatusState(status); + + if (!statusState) { + return null; + } + + if (status.isSavePaymentAccount) { + return getSavingMethodStatusConfig(statusState, status); + } + return getPaymentStatusConfig(statusState, status); + } +} diff --git a/src/features/headless-checkout/web-components/status/template-config/get-payment-status-config.function.ts b/src/features/headless-checkout/web-components/status/template-config/get-payment-status-config.function.ts new file mode 100644 index 00000000..b1734313 --- /dev/null +++ b/src/features/headless-checkout/web-components/status/template-config/get-payment-status-config.function.ts @@ -0,0 +1,62 @@ +import { StatusState } from './status-state.enum'; +import { Status } from '../../../../../core/status/status.interface'; +import { StatusComponentConfig } from './status.component.config.interface'; +import i18next from 'i18next'; +import { cdnIconsUrl } from '../../../environment'; + +const failedImage = `${cdnIconsUrl}/statuses/failed.png`; +const successImage = `${cdnIconsUrl}/statuses/success.png`; + +export function getPaymentStatusConfig( + statusState: StatusState, + status: Status, +): StatusComponentConfig | null { + // check cancel before processing since canceled invoice has status state "created" + if (statusState === StatusState.isCanceled) { + return { + image: failedImage, + title: i18next.t('status.error.title'), + description: i18next.t('error.title.default'), + showDescription: true, + status: status.statusState, + autoCancellation: status.autoCancellation, + }; + } + + if (statusState === StatusState.isProcessing) { + return { + image: null, + title: i18next.t('status.processing.title'), + description: i18next.t('status.processing.description'), + showDescription: true, + status: status.statusState, + autoCancellation: status.autoCancellation, + }; + } + + if (statusState === StatusState.isError) { + return { + image: failedImage, + title: i18next.t('status.error.title'), + description: i18next.t('error.title.default'), + showDescription: true, + status: status.statusState, + autoCancellation: status.autoCancellation, + }; + } + + if (statusState === StatusState.isSuccess) { + return { + image: successImage, + title: i18next.t('status.success.title'), + description: i18next.t('status.success.description', { + email: status.email, + }), + showDescription: !!status.email, + status: status.statusState, + autoCancellation: status.autoCancellation, + }; + } + + return null; +} diff --git a/src/features/headless-checkout/web-components/status/template-config/get-saving-method-status-config.function.ts b/src/features/headless-checkout/web-components/status/template-config/get-saving-method-status-config.function.ts new file mode 100644 index 00000000..5b8e062d --- /dev/null +++ b/src/features/headless-checkout/web-components/status/template-config/get-saving-method-status-config.function.ts @@ -0,0 +1,63 @@ +import { StatusState } from './status-state.enum'; +import { StatusComponentConfig } from './status.component.config.interface'; +import i18next from 'i18next'; +import { Status } from '../../../../../core/status/status.interface'; +import { cdnIconsUrl } from '../../../environment'; + +const failedImage = `${cdnIconsUrl}/statuses/failed.png`; +const successImage = `${cdnIconsUrl}/statuses/success.png`; + +export function getSavingMethodStatusConfig( + statusState: StatusState, + status: Status, +): StatusComponentConfig | null { + if (statusState === StatusState.isCanceled) { + return { + image: failedImage, + title: i18next.t('saving-status.error.title'), + description: '', + showDescription: false, + isSavePaymentAccount: true, + status: status.statusState, + autoCancellation: status.autoCancellation, + }; + } + + if (statusState === StatusState.isProcessing) { + return { + image: null, + title: i18next.t('saving-status.processing.title'), + description: i18next.t('saving-status.processing.description'), + showDescription: true, + isSavePaymentAccount: true, + status: status.statusState, + autoCancellation: status.autoCancellation, + }; + } + + if (statusState === StatusState.isError) { + return { + image: failedImage, + title: i18next.t('saving-status.error.title'), + description: '', + showDescription: false, + isSavePaymentAccount: true, + status: status.statusState, + autoCancellation: status.autoCancellation, + }; + } + + if (statusState === StatusState.isSuccess) { + return { + image: successImage, + title: i18next.t('saving-status.success.title'), + description: '', + showDescription: false, + isSavePaymentAccount: true, + status: status.statusState, + autoCancellation: status.autoCancellation, + }; + } + + return null; +} diff --git a/src/features/headless-checkout/web-components/status/template-config/get-status-state.function.spec.ts b/src/features/headless-checkout/web-components/status/template-config/get-status-state.function.spec.ts new file mode 100644 index 00000000..6f3a74a2 --- /dev/null +++ b/src/features/headless-checkout/web-components/status/template-config/get-status-state.function.spec.ts @@ -0,0 +1,27 @@ +import { getStatusState } from './get-status-state.function'; +import { StatusEnum } from '../../../../../core/status/status.enum'; +import { Status } from '../../../../../core/status/status.interface'; +import { StatusState } from './status-state.enum'; + +describe('getStatusState', () => { + it('Should return processing state', () => { + expect( + getStatusState({ statusState: StatusEnum.processing } as Status) + ).toEqual(StatusState.isProcessing); + }); + it('Should return cancel state', () => { + expect( + getStatusState({ statusState: StatusEnum.canceled } as Status) + ).toEqual(StatusState.isCanceled); + }); + it('Should return error state', () => { + expect(getStatusState({ statusState: StatusEnum.error } as Status)).toEqual( + StatusState.isError + ); + }); + it('Should return success state', () => { + expect(getStatusState({ statusState: StatusEnum.done } as Status)).toEqual( + StatusState.isSuccess + ); + }); +}); diff --git a/src/features/headless-checkout/web-components/status/template-config/get-status-state.function.ts b/src/features/headless-checkout/web-components/status/template-config/get-status-state.function.ts new file mode 100644 index 00000000..7d569b45 --- /dev/null +++ b/src/features/headless-checkout/web-components/status/template-config/get-status-state.function.ts @@ -0,0 +1,36 @@ +import { Status } from '../../../../../core/status/status.interface'; +import { StatusState } from './status-state.enum'; +import { StatusEnum } from '../../../../../core/status/status.enum'; + +export function getStatusState(status: Status): StatusState | null { + const isCanceled = + status.statusState === StatusEnum.canceled || status.isCancelUser; + + if (isCanceled) { + return StatusState.isCanceled; + } + + const isProcessing = [ + StatusEnum.processing, + StatusEnum.created, + StatusEnum.held, + ].includes(status.statusState); + if (isProcessing) { + return StatusState.isProcessing; + } + + const isError = status.statusState === StatusEnum.error; + + if (isError) { + return StatusState.isError; + } + + const isSuccess = + status.statusState === StatusEnum.done || + status.statusState === StatusEnum.authorized; + + if (isSuccess) { + return StatusState.isSuccess; + } + return null; +} diff --git a/src/features/headless-checkout/web-components/status/template-config/status-state.enum.ts b/src/features/headless-checkout/web-components/status/template-config/status-state.enum.ts new file mode 100644 index 00000000..06f23ef0 --- /dev/null +++ b/src/features/headless-checkout/web-components/status/template-config/status-state.enum.ts @@ -0,0 +1,6 @@ +export enum StatusState { + isProcessing = 'isProcessing', + isCanceled = 'isCanceled', + isError = 'isError', + isSuccess = 'isSuccess', +} diff --git a/src/features/headless-checkout/web-components/status/template-config/status.component.config.interface.ts b/src/features/headless-checkout/web-components/status/template-config/status.component.config.interface.ts new file mode 100644 index 00000000..a65d53c1 --- /dev/null +++ b/src/features/headless-checkout/web-components/status/template-config/status.component.config.interface.ts @@ -0,0 +1,11 @@ +import { StatusEnum } from '../../../../../core/status/status.enum'; + +export interface StatusComponentConfig { + title: string; + image: string | null; + description: string; + showDescription: boolean; + isSavePaymentAccount?: boolean; + status: StatusEnum; + autoCancellation: boolean; +} diff --git a/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button-attributes.enum.ts b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button-attributes.enum.ts new file mode 100644 index 00000000..2a275355 --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button-attributes.enum.ts @@ -0,0 +1,4 @@ +export enum DefaultSubmitButtonAttributes { + text = 'text', + isLoading = 'isLoading', +} diff --git a/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component.scss b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component.scss new file mode 100644 index 00000000..5f60a107 --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component.scss @@ -0,0 +1,35 @@ +@use 'src/styles/mixins/typo.mixin' as typo; +@use 'src/styles/mixins/animation.mixin' as animation; + +psdk-default-submit-button { + @include typo.psdk-typo; + + button { + @include typo.psdk-typo; + + width: 100%; + max-width: 360px; + height: 40px; + border: none; + border-radius: var(--psdk-button-border-radius); + background: var(--psdk-button-default-bg); + color: var(--psdk-button-color); + font-weight: 500; + + &:hover { + background: var(--psdk-button-hover-bg); + } + + &:active { + background: var(--psdk-button-press-bg); + } + + &:disabled { + background: var(--psdk-button-disabled-bg); + } + + .loader { + @include animation.loader(var(--psdk-button-color)); + } + } +} diff --git a/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component.spec.ts b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component.spec.ts new file mode 100644 index 00000000..6e3d4935 --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component.spec.ts @@ -0,0 +1,87 @@ +import { container } from 'tsyringe'; +import { DefaultSubmitButtonComponent } from './default-submit-button.component'; +import { PostMessagesClient } from '../../../../../core/post-messages-client/post-messages-client'; +import { WebComponentTagName } from '../../../../../core/web-components/web-component-tag-name.enum'; +import { HeadlessCheckout } from '../../../headless-checkout'; +import { noopStub } from '../../../../../tests/stubs/noop.stub'; +import { FormSpy } from '../../../../../core/spy/form-spy/form-spy'; + +function createComponent(): void { + const element = document.createElement( + WebComponentTagName.DefaultSubmitButtonComponent + ); + element.setAttribute('text', 'Pay Now'); + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +describe('DefaultSubmitButtonComponent', () => { + let postMessagesClient: PostMessagesClient; + let headlessCheckout: HeadlessCheckout; + let formSpy: FormSpy; + + window.customElements.define( + WebComponentTagName.DefaultSubmitButtonComponent, + DefaultSubmitButtonComponent + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + postMessagesClient = { + init: noopStub, + send: noopStub, + } as unknown as PostMessagesClient; + + headlessCheckout = { + form: { + onNextAction: noopStub, + onFieldsStatusChange: noopStub, + }, + } as unknown as HeadlessCheckout; + + formSpy = { + listenFormInit: noopStub, + get formWasInit() { + return; + }, + } as unknown as FormSpy; + + container.clearInstances(); + + container + .register(FormSpy, { + useValue: formSpy, + }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); + }); + + it('Should set text for button', () => { + createComponent(); + + expect(document.querySelector('button')!.textContent).toContain('Pay Now'); + }); + + it('Should sendPostMessage event', () => { + const spy = spyOn(postMessagesClient, 'send'); + + createComponent(); + + const button = document.querySelector('button'); + + button!.removeAttribute('disabled'); + button!.click(); + + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component.ts b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component.ts new file mode 100644 index 00000000..cf0c57dc --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.component.ts @@ -0,0 +1,119 @@ +import { container } from 'tsyringe'; +import { WebComponentAbstract } from '../../../../../core/web-components/web-component.abstract'; +import { PostMessagesClient } from '../../../../../core/post-messages-client/post-messages-client'; +import { EventName } from '../../../../../core/event-name.enum'; +import { HeadlessCheckout } from '../../../headless-checkout'; +import { DefaultSubmitButtonAttributes } from './default-submit-button-attributes.enum'; +import { getSubmitButtonTemplate } from './default-submit-button.template'; +import { defaultSubmitButtonHandler } from './default-submit-button.handler'; +import { Message } from '../../../../../core/message.interface'; +import { Handler } from '../../../../../core/post-messages-client/handler.type'; +import { isSubmitButtonLoadingMessage } from '../../../../../core/guards/submit-button-loading-message.guard'; +import { isShowFieldsAction } from '../../../../../core/actions/is-show-fields-action.function'; +import './default-submit-button.component.scss'; + +export class DefaultSubmitButtonComponent extends WebComponentAbstract { + private readonly postMessagesClient: PostMessagesClient; + private readonly headlessCheckout: HeadlessCheckout; + + private get elementRef(): HTMLButtonElement { + return this.querySelector('button')! as HTMLButtonElement; + } + + public constructor() { + super(); + this.postMessagesClient = container.resolve(PostMessagesClient); + this.headlessCheckout = container.resolve(HeadlessCheckout); + } + + public static get observedAttributes(): string[] { + return [ + DefaultSubmitButtonAttributes.isLoading, + DefaultSubmitButtonAttributes.text, + ]; + } + + protected connectedCallback(): void { + super.connectedCallback(); + this.listenFormInit(); + } + + protected render(): void { + this.removeAllEventListeners(); + this.innerHTML = this.getHtml(); + if (this.elementRef) { + this.addEventListenerToElement(this.elementRef, 'click', () => { + if (this.getAttribute(DefaultSubmitButtonAttributes.isLoading)) { + return; + } + + void this.postMessagesClient.send( + { name: EventName.getFormStatus }, + this.loadingHandler, + ); + + let isCheckedFieldStatuses = false; + this.headlessCheckout.form.onFieldsStatusChange((fieldsStatus) => { + if (isCheckedFieldStatuses) { + return; + } + + isCheckedFieldStatuses = true; + + const isInvalidForm = Object.entries(fieldsStatus).some((fields) => { + const [, value] = fields; + + return value.validationStatus === 'INVALID'; + }); + + if (isInvalidForm) { + this.removeAttribute(DefaultSubmitButtonAttributes.isLoading); + + this.render(); + } + }); + + void this.postMessagesClient.send( + { name: EventName.submitForm }, + defaultSubmitButtonHandler, + ); + + this.render(); + }); + } + } + + protected getHtml(): string { + const text = this.getAttribute(DefaultSubmitButtonAttributes.text) ?? ''; + const isLoading = !!this.getAttribute( + DefaultSubmitButtonAttributes.isLoading, + ); + + return getSubmitButtonTemplate(text, isLoading); + } + + private listenFormInit(): void { + this.headlessCheckout.form.onNextAction((nextAction) => { + if (isShowFieldsAction(nextAction)) { + this.removeAttribute(DefaultSubmitButtonAttributes.isLoading); + this.render(); + } + }); + } + + private readonly loadingHandler: Handler = ( + message: Message, + ): { isHandled: boolean } | null => { + if (!isSubmitButtonLoadingMessage(message)) return null; + + const isCanClick = message.data?.formStatus === 'VALID'; + + if (isCanClick) { + this.setAttribute(DefaultSubmitButtonAttributes.isLoading, 'true'); + this.render(); + } + return { + isHandled: true, + }; + }; +} diff --git a/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.handler.spec.ts b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.handler.spec.ts new file mode 100644 index 00000000..6af4bbf6 --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.handler.spec.ts @@ -0,0 +1,21 @@ +import { EventName } from '../../../../../core/event-name.enum'; +import { Message } from '../../../../../core/message.interface'; +import { PaymentMethod } from '../../../../../core/payment-method.interface'; +import { defaultSubmitButtonHandler } from './default-submit-button.handler'; + +const mockMessage: Message<{ methods: PaymentMethod[] }> = { + name: EventName.submitForm, + data: { methods: [] }, +}; +describe('submitButtonHandler', () => { + it('Should handle data', () => { + expect(defaultSubmitButtonHandler(mockMessage)).toEqual({ + isHandled: true, + }); + }); + it('Should return null', () => { + expect( + defaultSubmitButtonHandler({ name: EventName.initPayment }) + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.handler.ts b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.handler.ts new file mode 100644 index 00000000..620d48db --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.handler.ts @@ -0,0 +1,14 @@ +import { Handler } from '../../../../../core/post-messages-client/handler.type'; +import { Message } from '../../../../../core/message.interface'; +import { EventName } from '../../../../../core/event-name.enum'; + +export const defaultSubmitButtonHandler: Handler = ( + message: Message +): { isHandled: boolean } | null => { + if (message.name === EventName.submitForm) { + return { + isHandled: true, + }; + } + return null; +}; diff --git a/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.template.ts b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.template.ts new file mode 100644 index 00000000..db74cec5 --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/default-submit-button/default-submit-button.template.ts @@ -0,0 +1,16 @@ +const getLoaderTemplate = (): string => { + return ` +
    + `; +}; + +export const getSubmitButtonTemplate = ( + text: string, + isLoading: boolean +): string => { + return ` + + `; +}; diff --git a/src/features/headless-checkout/web-components/submit-button/submit-button.component.spec.ts b/src/features/headless-checkout/web-components/submit-button/submit-button.component.spec.ts new file mode 100644 index 00000000..83da35a3 --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/submit-button.component.spec.ts @@ -0,0 +1,98 @@ +import { container } from 'tsyringe'; +import { SubmitButtonComponent } from './submit-button.component'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { applePayId } from '../apple-pay/apple-pay-id.const'; +import { FormConfiguration } from '../../../../core/form/form-configuration.interface'; + +function createComponent(): void { + const element = document.createElement( + WebComponentTagName.SubmitButtonComponent + ); + element.setAttribute('text', 'Pay Now'); + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +describe('SubmitButtonComponent', () => { + let headlessCheckout: HeadlessCheckout; + let formSpy: FormSpy; + let postMessagesClient: PostMessagesClient; + + window.customElements.define( + WebComponentTagName.SubmitButtonComponent, + SubmitButtonComponent + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + postMessagesClient = { + init: noopStub, + send: noopStub, + } as unknown as PostMessagesClient; + + headlessCheckout = { + form: { + onNextAction: noopStub, + onFieldsStatusChange: noopStub, + }, + get formConfiguration() { + return null; + }, + events: { + onCoreEvent: noopStub, + }, + } as unknown as HeadlessCheckout; + + formSpy = { + listenFormInit: noopStub, + get formWasInit() { + return; + }, + } as unknown as FormSpy; + + container.clearInstances(); + + container + .register(FormSpy, { + useValue: formSpy, + }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }) + .register(Window, { + useValue: window, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); + }); + + it('Should draw default button', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.SubmitButtonComponent)! + .innerHTML + ).toContain(WebComponentTagName.DefaultSubmitButtonComponent); + }); + + it('Should draw apple pay button', () => { + spyOnProperty(headlessCheckout, 'formConfiguration', 'get').and.returnValue( + { paymentMethodId: applePayId } as FormConfiguration + ); + createComponent(); + expect( + document.querySelector(WebComponentTagName.SubmitButtonComponent)! + .innerHTML + ).toContain(WebComponentTagName.ApplePayComponent); + }); +}); diff --git a/src/features/headless-checkout/web-components/submit-button/submit-button.component.ts b/src/features/headless-checkout/web-components/submit-button/submit-button.component.ts new file mode 100644 index 00000000..94e5929e --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/submit-button.component.ts @@ -0,0 +1,59 @@ +import { container } from 'tsyringe'; +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { applePayId } from '../apple-pay/apple-pay-id.const'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { DefaultSubmitButtonAttributes } from './default-submit-button/default-submit-button-attributes.enum'; +import { getSubmitButtonTemplate } from './submit-button.template'; + +export class SubmitButtonComponent extends WebComponentAbstract { + private readonly headlessCheckout: HeadlessCheckout; + private readonly formSpy: FormSpy; + private wasRendered = false; + private get isApplePayButton(): boolean { + return ( + this.headlessCheckout.formConfiguration?.paymentMethodId === applePayId + ); + } + + public static get observedAttributes(): string[] { + return [ + DefaultSubmitButtonAttributes.isLoading, + DefaultSubmitButtonAttributes.text, + ]; + } + + public constructor() { + super(); + this.headlessCheckout = container.resolve(HeadlessCheckout); + this.formSpy = container.resolve(FormSpy); + } + + protected connectedCallback(): void { + if (!this.formSpy.formWasInit) { + this.formSpy.listenFormInit(() => this.connectedCallback()); + return; + } + + this.render(); + } + + protected render(): void { + if (this.isApplePayButton && this.wasRendered) { + return; + } + this.wasRendered = true; + super.render(); + } + + protected getHtml(): string { + if (this.isApplePayButton) { + return `<${WebComponentTagName.ApplePayComponent}>`; + } + return getSubmitButtonTemplate( + this.getAttribute(DefaultSubmitButtonAttributes.text) ?? '', + !!this.getAttribute(DefaultSubmitButtonAttributes.isLoading) + ); + } +} diff --git a/src/features/headless-checkout/web-components/submit-button/submit-button.template.ts b/src/features/headless-checkout/web-components/submit-button/submit-button.template.ts new file mode 100644 index 00000000..7f588ac9 --- /dev/null +++ b/src/features/headless-checkout/web-components/submit-button/submit-button.template.ts @@ -0,0 +1,20 @@ +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { DefaultSubmitButtonAttributes } from './default-submit-button/default-submit-button-attributes.enum'; + +export const getSubmitButtonTemplate = ( + text: string, + isLoading: boolean +): string => { + let attributes = ''; + if (text) { + attributes += `${DefaultSubmitButtonAttributes.text}='${text}'`; + } + if (isLoading) { + attributes += `${DefaultSubmitButtonAttributes.isLoading}=${ + isLoading && 'true' + }`; + } + return ` + <${WebComponentTagName.DefaultSubmitButtonComponent} ${attributes}> + `; +}; diff --git a/src/features/headless-checkout/web-components/text-component/text-component-attributes.enum.ts b/src/features/headless-checkout/web-components/text-component/text-component-attributes.enum.ts new file mode 100644 index 00000000..bea44989 --- /dev/null +++ b/src/features/headless-checkout/web-components/text-component/text-component-attributes.enum.ts @@ -0,0 +1,3 @@ +export enum TextComponentAttributes { + name = 'name', +} diff --git a/src/features/headless-checkout/web-components/text-component/text-component.config.interface.ts b/src/features/headless-checkout/web-components/text-component/text-component.config.interface.ts new file mode 100644 index 00000000..2835e749 --- /dev/null +++ b/src/features/headless-checkout/web-components/text-component/text-component.config.interface.ts @@ -0,0 +1,9 @@ +import { ControlComponentConfig } from '../control-component-config.interface'; +import { TextConfigTooltip } from './text-config-tooltip.interface'; +import { TemplateResult } from 'lit'; + +export interface TextComponentConfig extends ControlComponentConfig { + secureHtml: string; + additionalControls?: TemplateResult<1>; + tooltip?: TextConfigTooltip; +} diff --git a/src/features/headless-checkout/web-components/text-component/text-config-tooltip.interface.ts b/src/features/headless-checkout/web-components/text-component/text-config-tooltip.interface.ts new file mode 100644 index 00000000..cadef4b2 --- /dev/null +++ b/src/features/headless-checkout/web-components/text-component/text-config-tooltip.interface.ts @@ -0,0 +1,4 @@ +export interface TextConfigTooltip { + text: string; + image?: string; +} diff --git a/src/features/headless-checkout/web-components/text-component/text.component.spec.ts b/src/features/headless-checkout/web-components/text-component/text.component.spec.ts new file mode 100644 index 00000000..a9c6e504 --- /dev/null +++ b/src/features/headless-checkout/web-components/text-component/text.component.spec.ts @@ -0,0 +1,212 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { TextComponent } from './text.component'; +import { FormFieldsStatus } from '../../../../core/form/form-fields-status.interface'; + +const fieldName = 'zip'; + +const emptyFieldsStatus: FormFieldsStatus = {}; + +const validFieldsStatus: FormFieldsStatus = { + [fieldName]: { + name: fieldName, + validationStatus: 'VALID', + errors: null, + }, +}; + +const invalidFieldsStatus: FormFieldsStatus = { + [fieldName]: { + name: fieldName, + validationStatus: 'INVALID', + isTouched: true, + errors: { + errorName: { + message: 'message', + }, + }, + }, +}; + +const focusedInvalidFieldsStatus: FormFieldsStatus = { + [fieldName]: { + name: fieldName, + validationStatus: 'INVALID', + isFocused: true, + errors: { + errorName: { + message: 'message', + }, + }, + }, +}; + +function createComponent(): HTMLElement { + const element = document.createElement(WebComponentTagName.TextComponent); + element.setAttribute('name', fieldName); + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); + return element; +} + +describe('TextComponent', () => { + let postMessagesClient: PostMessagesClient; + let headlessCheckout: HeadlessCheckout; + let formSpy: FormSpy; + + window.customElements.define( + WebComponentTagName.TextComponent, + TextComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + postMessagesClient = { + send: noopStub, + } as unknown as PostMessagesClient; + + headlessCheckout = { + form: { + onFieldsStatusChange: noopStub, + }, + events: { + onCoreEvent: noopStub, + }, + } as unknown as HeadlessCheckout; + + formSpy = { + listenFormInit: noopStub, + get formWasInit() { + return; + }, + } as unknown as FormSpy; + + container.clearInstances(); + + container + .register(FormSpy, { + useValue: formSpy, + }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }) + .register(HeadlessCheckout, { + useValue: headlessCheckout, + }) + .register(Window, { + useValue: window, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Web component should be rendered', () => { + spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); + const postMessageSpy = spyOn(postMessagesClient, 'send').and.stub(); + + createComponent(); + + expect(document.querySelector('iframe')).toBeDefined(); + expect(document.getElementsByClassName('label')).toBeDefined(); + expect(postMessageSpy).toHaveBeenCalled(); + }); + + it('Should not render error element for valid field state', (done) => { + let element: HTMLElement = document.createElement('div'); + let callback: (fieldsStatus: FormFieldsStatus) => void = noopStub; + spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); + spyOn(postMessagesClient, 'send').and.resolveTo({ + name: fieldName, + }); + spyOn(headlessCheckout.form, 'onFieldsStatusChange').and.callFake( + (callbackFn) => { + setTimeout(() => (callback = callbackFn)); + }, + ); + + element = createComponent(); + + setTimeout(() => { + callback(validFieldsStatus); + expect(element.getElementsByClassName('field-error')[0]).toBeUndefined(); + done(); + }); + }); + + it('Should not render error element if no current field state', (done) => { + let element: HTMLElement = document.createElement('div'); + let callback: (fieldsStatus: FormFieldsStatus) => void = noopStub; + spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); + spyOn(postMessagesClient, 'send').and.resolveTo({ + name: fieldName, + }); + spyOn(headlessCheckout.form, 'onFieldsStatusChange').and.callFake( + (callbackFn) => { + setTimeout(() => (callback = callbackFn)); + }, + ); + + element = createComponent(); + + setTimeout(() => { + callback(emptyFieldsStatus); + expect(element.getElementsByClassName('field-error')[0]).toBeUndefined(); + done(); + }); + }); + + it('Should render error element', (done) => { + let element: HTMLElement = document.createElement('div'); + let callback: (fieldsStatus: FormFieldsStatus) => void = noopStub; + spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); + spyOn(postMessagesClient, 'send').and.resolveTo({ + name: fieldName, + }); + spyOn(headlessCheckout.form, 'onFieldsStatusChange').and.callFake( + (callbackFn) => { + setTimeout(() => (callback = callbackFn)); + }, + ); + + element = createComponent(); + + setTimeout(() => { + callback(invalidFieldsStatus); + expect(element.getElementsByClassName('field-error')[0]).toBeDefined(); + done(); + }); + }); + + it('Should remove error element', (done) => { + let element: HTMLElement = document.createElement('div'); + let callback: (fieldsStatus: FormFieldsStatus) => void = noopStub; + spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); + spyOn(postMessagesClient, 'send').and.resolveTo({ + name: fieldName, + }); + spyOn(headlessCheckout.form, 'onFieldsStatusChange').and.callFake( + (callbackFn) => { + setTimeout(() => (callback = callbackFn)); + }, + ); + + element = createComponent(); + + setTimeout(() => { + callback(invalidFieldsStatus); + expect(element.getElementsByClassName('field-error')[0]).toBeDefined(); + + callback(focusedInvalidFieldsStatus); + expect(element.getElementsByClassName('field-error')[0]).toBeUndefined(); + + done(); + }); + }); +}); diff --git a/src/features/headless-checkout/web-components/text-component/text.component.ts b/src/features/headless-checkout/web-components/text-component/text.component.ts new file mode 100644 index 00000000..e0ea5426 --- /dev/null +++ b/src/features/headless-checkout/web-components/text-component/text.component.ts @@ -0,0 +1,180 @@ +import { TextComponentAttributes } from './text-component-attributes.enum'; +import { container } from 'tsyringe'; +import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { EventName } from '../../../../core/event-name.enum'; +import { Message } from '../../../../core/message.interface'; +import { ControlComponentConfig } from '../control-component-config.interface'; +import { getControlComponentConfigHandler } from '../get-control-component-config.handler'; +import { HeadlessCheckout } from '../../headless-checkout'; +import { ValidationErrors } from '../../../../core/form/validation-errors.interface'; +import { TextComponentConfig } from './text-component.config.interface'; +import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler'; +import { LitSecureComponentAbstract } from '../../../../core/web-components/secure-component/lit-secure-component.abstract'; +import { html, TemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; + +@customElement('psdk-text') +export class TextComponent extends LitSecureComponentAbstract { + @property({ attribute: TextComponentAttributes.name }) + protected inputName = ''; + + @property({ attribute: false }) + protected config?: TextComponentConfig; + + @property({ attribute: false }) + protected showError: boolean = false; + + protected readonly postMessagesClient: PostMessagesClient = + container.resolve(PostMessagesClient); + protected readonly window: Window = container.resolve(Window); + protected readonly formSpy: FormSpy = container.resolve(FormSpy); + + private readonly headlessCheckout: HeadlessCheckout = + container.resolve(HeadlessCheckout); + private isListeningFieldStatusChange = false; + + public constructor() { + super(); + + this.headlessCheckout.events.onCoreEvent( + EventName.finishLoadComponent, + finishLoadComponentHandler, + (res) => { + if (res?.fieldName && res?.fieldName === this.inputName) { + this.finishLoadingFormControlHandler(this.inputName); + } + }, + ); + } + + public connectedCallback(): void { + super.connectedCallback(); + + if (!this.formSpy.formWasInit) { + this.formSpy.listenFormInit(() => this.getConfigFromInputName()); + return; + } + + this.getConfigFromInputName(); + } + + protected render(): TemplateResult<1> { + if (!this.formSpy.formWasInit || !this.componentName) { + return html``; + } + + return this.getTextComponentTemplate(); + } + + protected getTextComponentTemplate( + additionalControls?: TemplateResult<1> | null, + ): TemplateResult<1> { + const { title, tooltip, error } = this.config ?? {}; + + return html` + ${this.renderByCondition( + !!title, + html`
    ${title}
    `, + )} + ${this.renderByCondition( + !!tooltip?.text, + html`
    ${tooltip?.text}
    `, + )} + ${this.renderSecureComponentWithWrapper(additionalControls)} + ${this.renderByCondition( + this.showError, + html`
    ${error}
    `, + )} + `; + } + + protected async getControlComponentConfig( + inputName: string, + ): Promise { + const msg: Message<{ inputName: string }> = { + name: EventName.getControlComponentConfig, + data: { + inputName, + }, + }; + + return this.postMessagesClient.send( + msg, + (message) => { + return getControlComponentConfigHandler(message, (controlName) => { + return msg.data?.inputName === controlName; + }); + }, + ) as Promise; + } + + protected readonly configLoadedHandler = ( + config: TextComponentConfig, + componentName: string, + ): void => { + this.config = config; + this.componentName = componentName; + }; + + private getConfigFromInputName(): void { + if (!this.inputName) { + return; + } + + this.listenFieldStatusChange(this.inputName); + + void this.getControlComponentConfig(this.inputName).then((config) => { + this.configLoadedHandler(config, `text-input/${this.inputName!}`); + }); + } + + private listenFieldStatusChange(controlName: string): void { + if (this.isListeningFieldStatusChange) { + return; + } + + this.isListeningFieldStatusChange = true; + this.headlessCheckout.form.onFieldsStatusChange((fieldsStatus) => { + const fieldStatus = fieldsStatus[controlName]; + + if (!this.config || !fieldStatus) { + return; + } + + this.showError = !!(fieldStatus.isTouched && !fieldStatus.isFocused); + + this.config = { + ...this.config, + error: this.getFirstError(fieldStatus.errors), + }; + }); + } + + private getFirstError(errors: ValidationErrors | null): string | null { + if (!errors) { + return null; + } + + const firstErrorKey: string = Object.keys(errors)[0]; + return errors[firstErrorKey]?.message ?? null; + } + + private renderSecureComponentWithWrapper( + additionalControls?: TemplateResult<1> | null, + ): TemplateResult<1> { + const additionalClass = additionalControls + ? 'wrapper--additional-controls' + : ''; + return html` +
    + ${unsafeHTML(this.getSecureHtml())} + ${this.renderByCondition( + !!additionalControls, + html`${additionalControls}`, + )} +
    + `; + } +} diff --git a/src/features/headless-checkout/web-components/three-ds/three-ds-attributes.enum.ts b/src/features/headless-checkout/web-components/three-ds/three-ds-attributes.enum.ts new file mode 100644 index 00000000..9156472a --- /dev/null +++ b/src/features/headless-checkout/web-components/three-ds/three-ds-attributes.enum.ts @@ -0,0 +1,3 @@ +export enum ThreeDsAttributes { + challenge = 'data-challenge', +} diff --git a/src/features/headless-checkout/web-components/three-ds/three-ds.component.spec.ts b/src/features/headless-checkout/web-components/three-ds/three-ds.component.spec.ts new file mode 100644 index 00000000..a11e0c0e --- /dev/null +++ b/src/features/headless-checkout/web-components/three-ds/three-ds.component.spec.ts @@ -0,0 +1,95 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { CheckoutForm } from '../../../../core/actions/three-ds/checkout-form.interface'; +import { ThreeDsComponent } from './three-ds.component'; +import createSpy = jasmine.createSpy; + +function createComponent(): void { + const element = document.createElement(WebComponentTagName.ThreeDsComponent); + + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +function get3dsComponent(): ThreeDsComponent { + return document.querySelector( + WebComponentTagName.ThreeDsComponent, + ) as unknown as ThreeDsComponent; +} + +describe('ThreeDsComponent', () => { + let windowService: Window; + + window.customElements.define( + WebComponentTagName.ThreeDsComponent, + ThreeDsComponent, + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + windowService = { + document: { + createElement: noopStub, + }, + open: noopStub, + } as unknown as Window; + + container.clearInstances(); + + container.register(Window, { + useValue: windowService, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.ThreeDsComponent), + ).toBeDefined(); + }); + + it('should open 3ds window', () => { + const submitSpy = createSpy('submitSpy'); + + const formElement = window.document.createElement('form'); + formElement.submit = submitSpy; + const inputElement = window.document.createElement('input'); + + const challengeForm = { + action: 'GET', + method: 'method', + params: { + foo: 'bar', + }, + } as CheckoutForm; + + const createElementSpy = spyOn( + windowService.document, + 'createElement', + ).and.callFake((tagName: string) => { + if (tagName === 'form') { + return formElement; + } + + return inputElement; + }); + const windowOpenSpy = spyOn(windowService, 'open').and.stub(); + + createComponent(); + + const threeDsComponent = get3dsComponent(); + threeDsComponent.setAttribute( + 'data-challenge', + JSON.stringify(challengeForm), + ); + + expect(createElementSpy).toHaveBeenCalledTimes(2); + expect(windowOpenSpy).toHaveBeenCalled(); + expect(submitSpy).toHaveBeenCalled(); + }); +}); diff --git a/src/features/headless-checkout/web-components/three-ds/three-ds.component.ts b/src/features/headless-checkout/web-components/three-ds/three-ds.component.ts new file mode 100644 index 00000000..aca9b69f --- /dev/null +++ b/src/features/headless-checkout/web-components/three-ds/three-ds.component.ts @@ -0,0 +1,98 @@ +import { container } from 'tsyringe'; +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { CheckoutForm } from '../../../../core/actions/three-ds/checkout-form.interface'; +import { ThreeDsAttributes } from './three-ds-attributes.enum'; + +export class ThreeDsComponent extends WebComponentAbstract { + private readonly window: Window; + + private get elementRef(): HTMLElement { + return this.querySelector('div')! as HTMLElement; + } + + private get challengeAttribute(): CheckoutForm | null { + const challengeString = this.getAttribute(ThreeDsAttributes.challenge); + + if (!challengeString) { + return null; + } + + try { + return JSON.parse(challengeString) as CheckoutForm; + } catch (error: unknown) { + return null; + } + } + + public constructor() { + super(); + + this.window = container.resolve(Window); + } + + public static get observedAttributes(): string[] { + return [ThreeDsAttributes.challenge]; + } + + protected connectedCallback(): void { + if (this.challengeAttribute) { + this.render(); + + this.openChallengeWindowViaForm(this.challengeAttribute); + } + } + + protected attributeChangedCallback(): void { + if (this.challengeAttribute) { + this.render(); + + this.openChallengeWindowViaForm(this.challengeAttribute); + } + } + + protected getHtml(): string { + return '
    '; + } + + private openChallengeWindowViaForm(checkoutFormData: CheckoutForm): void { + const target = '_self'; + const attributes: { [key: string]: string } = { + method: checkoutFormData.method, + action: checkoutFormData.action, + target, + }; + + const form = this.window.document.createElement('form'); + + this.setElementAttributes(form, attributes); + + if (checkoutFormData.params) { + for (const param in checkoutFormData.params) { + const input = this.window.document.createElement('input'); + input.type = 'hidden'; + input.name = param; + input.value = checkoutFormData.params[param]; + + form.appendChild(input); + } + } + + this.elementRef.append(form); + + this.window.open('', target); + + form.target = target; + form.submit(); + + this.elementRef.removeChild(form); + } + + private setElementAttributes( + element: HTMLElement, + attributes: { [key: string]: string }, + ): void { + Object.keys(attributes).forEach((name) => { + element.setAttribute(name, attributes[name]); + }); + } +} diff --git a/src/features/headless-checkout/web-components/user-balance-component/user-balance-attributes.enum.ts b/src/features/headless-checkout/web-components/user-balance-component/user-balance-attributes.enum.ts new file mode 100644 index 00000000..41b179a3 --- /dev/null +++ b/src/features/headless-checkout/web-components/user-balance-component/user-balance-attributes.enum.ts @@ -0,0 +1,3 @@ +export enum UserBalanceAttributes { + noBalance = 'no-balance', +} diff --git a/src/features/headless-checkout/web-components/user-balance-component/user-balance-component.ts b/src/features/headless-checkout/web-components/user-balance-component/user-balance-component.ts new file mode 100644 index 00000000..025366cb --- /dev/null +++ b/src/features/headless-checkout/web-components/user-balance-component/user-balance-component.ts @@ -0,0 +1,54 @@ +import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { container } from 'tsyringe'; +import { Message } from '../../../../core/message.interface'; +import { EventName } from '../../../../core/event-name.enum'; +import { getUserBalanceValueHandler } from '../../post-messages-handlers/get-user-balance-value.handler'; +import { UserBalanceType } from './user-balance.type'; +import { getUserBalanceTemplate } from './user-balance.template'; +import { UserBalanceAttributes } from './user-balance-attributes.enum'; + +export class UserBalanceComponent extends WebComponentAbstract { + private readonly postMessagesClient: PostMessagesClient; + private readonly headlessCheckoutSpy: HeadlessCheckoutSpy; + private balance?: UserBalanceType; + + public constructor() { + super(); + this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy); + this.postMessagesClient = container.resolve(PostMessagesClient); + } + + protected connectedCallback(): void { + if (!this.headlessCheckoutSpy.appWasInit) { + this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); + return; + } + void this.getUserBalanceValue().then(this.configLoadedHandler); + } + + protected async getUserBalanceValue(): Promise { + const msg: Message = { + name: EventName.getUserBalanceValue, + }; + + return this.postMessagesClient.send( + msg, + getUserBalanceValueHandler + ) as Promise; + } + + protected getHtml(): string { + return getUserBalanceTemplate(this.balance, this.noBalanceTemplate); + } + + private get noBalanceTemplate(): string { + return this.getAttribute(UserBalanceAttributes.noBalance) ?? ''; + } + + private readonly configLoadedHandler = (balance: UserBalanceType): void => { + this.balance = balance; + super.render(); + }; +} diff --git a/src/features/headless-checkout/web-components/user-balance-component/user-balance.component.spec.ts b/src/features/headless-checkout/web-components/user-balance-component/user-balance.component.spec.ts new file mode 100644 index 00000000..418fd4a4 --- /dev/null +++ b/src/features/headless-checkout/web-components/user-balance-component/user-balance.component.spec.ts @@ -0,0 +1,90 @@ +import { container } from 'tsyringe'; +import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum'; +import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; +import { noopStub } from '../../../../tests/stubs/noop.stub'; +import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; +import { UserBalanceComponent } from './user-balance-component'; + +function createComponent(): void { + const element = document.createElement( + WebComponentTagName.UserBalanceComponent + ); + element.setAttribute('id', 'test'); + (document.getElementById('container')! as HTMLElement).appendChild(element); +} + +describe('UserBalanceComponent', () => { + let headlessCheckoutSpy: HeadlessCheckoutSpy; + let postMessagesClient: PostMessagesClient; + + window.customElements.define( + WebComponentTagName.UserBalanceComponent, + UserBalanceComponent + ); + + beforeEach(() => { + document.body.innerHTML = '
    '; + + headlessCheckoutSpy = { + listenAppInit: noopStub, + get appWasInit() { + return; + }, + } as unknown as HeadlessCheckoutSpy; + + postMessagesClient = { + send: noopStub, + } as unknown as PostMessagesClient; + + container.clearInstances(); + + container + .register(HeadlessCheckoutSpy, { + useValue: headlessCheckoutSpy, + }) + .register(PostMessagesClient, { + useValue: postMessagesClient, + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('Should create component', () => { + createComponent(); + expect( + document.querySelector(WebComponentTagName.UserBalanceComponent) + ).toBeDefined(); + }); + + it('Should load user balance', () => { + const spy = spyOn(postMessagesClient, 'send').and.returnValue( + Promise.resolve({}) + ); + spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue( + true + ); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); + + it('Should load user balance after init', () => { + const spy = spyOn(postMessagesClient, 'send').and.returnValue( + Promise.resolve({}) + ); + const appWasInitSpy = spyOnProperty( + headlessCheckoutSpy, + 'appWasInit', + 'get' + ); + const listenAppInitSpy = spyOn(headlessCheckoutSpy, 'listenAppInit'); + listenAppInitSpy.and.callFake((callback: () => void) => { + appWasInitSpy.and.returnValue(true); + callback(); + }); + appWasInitSpy.and.returnValue(false); + createComponent(); + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/src/features/headless-checkout/web-components/user-balance-component/user-balance.template.spec.ts b/src/features/headless-checkout/web-components/user-balance-component/user-balance.template.spec.ts new file mode 100644 index 00000000..4261400a --- /dev/null +++ b/src/features/headless-checkout/web-components/user-balance-component/user-balance.template.spec.ts @@ -0,0 +1,19 @@ +import { getUserBalanceTemplate } from './user-balance.template'; +import { Balance } from '../../../../core/balance.interface'; + +const mockBalance: Balance = { + amount: 10, + currency: 'USD', +}; + +const emptyBalance = 'No balance'; + +describe('getUserBalanceTemplate', () => { + it('Should contains user-balance', () => { + expect(getUserBalanceTemplate(mockBalance, '')).toContain('user-balance'); + }); + + it('Should contains no-balance string', () => { + expect(getUserBalanceTemplate(null, emptyBalance)).toContain(emptyBalance); + }); +}); diff --git a/src/features/headless-checkout/web-components/user-balance-component/user-balance.template.ts b/src/features/headless-checkout/web-components/user-balance-component/user-balance.template.ts new file mode 100644 index 00000000..b42296fb --- /dev/null +++ b/src/features/headless-checkout/web-components/user-balance-component/user-balance.template.ts @@ -0,0 +1,16 @@ +import { CurrencyPipe } from '../../../../core/pipes/currency/currency.pipe'; +import { container } from 'tsyringe'; +import { UserBalanceType } from './user-balance.type'; + +export const getUserBalanceTemplate = ( + balance: UserBalanceType, + noBalanceTemplate: string +): string => { + const currencyPipe = container.resolve(CurrencyPipe); + if (balance?.amount) { + return `
    ${ + currencyPipe.transform(balance.amount, balance.currency) ?? '' + }
    `; + } + return noBalanceTemplate; +}; diff --git a/src/features/headless-checkout/web-components/user-balance-component/user-balance.type.ts b/src/features/headless-checkout/web-components/user-balance-component/user-balance.type.ts new file mode 100644 index 00000000..123db5f5 --- /dev/null +++ b/src/features/headless-checkout/web-components/user-balance-component/user-balance.type.ts @@ -0,0 +1,3 @@ +import { Balance } from '../../../../core/balance.interface'; + +export type UserBalanceType = Balance | null | undefined; diff --git a/src/index.spec.ts b/src/index.spec.ts deleted file mode 100644 index 2fd8ac1b..00000000 --- a/src/index.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { fooSum } from './'; - -test('basic', () => { - expect(fooSum()).toBe(0); -}); - -test('basic again', () => { - const expected = 3; - expect(fooSum(1, 2)).toBe(expected); -}); diff --git a/src/index.ts b/src/index.ts index 0e522cd6..45a2f203 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,8 @@ -export const fooSum = (...a: number[]): number => - a.reduce((acc, val): number => acc + val, 0); +import 'reflect-metadata'; +import './styles/style.scss'; -export const message = 'message'; +export { headlessCheckout } from './features/headless-checkout/headless-checkout.instance'; +export * from './web-components'; +export { Lang } from './core/i18n/lang.enum'; +export { EventName } from './core/event-name.enum'; +export { NextActionType } from './core/actions/next-action-type.enum'; diff --git a/src/shared/loading-state/dispatch-finish-loading-event.function.ts b/src/shared/loading-state/dispatch-finish-loading-event.function.ts new file mode 100644 index 00000000..f7d20b45 --- /dev/null +++ b/src/shared/loading-state/dispatch-finish-loading-event.function.ts @@ -0,0 +1,13 @@ +import { EventName } from '../../core/event-name.enum'; + +export function createFinishLoadingEvent(componentName: string): CustomEvent { + const eventOptions = { + bubbles: true, + composed: true, + detail: { + componentName, + }, + }; + + return new CustomEvent(EventName.finishLoadComponent, eventOptions); +} diff --git a/src/shared/loading-state/is-loading-css-class-name.const.ts b/src/shared/loading-state/is-loading-css-class-name.const.ts new file mode 100644 index 00000000..28dab1dd --- /dev/null +++ b/src/shared/loading-state/is-loading-css-class-name.const.ts @@ -0,0 +1 @@ +export const isLoadingCssClassName = 'psdk-is-loading'; diff --git a/src/styles/font.scss b/src/styles/font.scss new file mode 100644 index 00000000..8d5adf80 --- /dev/null +++ b/src/styles/font.scss @@ -0,0 +1,29 @@ +@font-face { + font-weight: normal; + font-style: normal; + font-family: GraphikLCWebUikit; + src: url('https://cdn.xsolla.net/headless-checkout-prod/assets/fonts/graphik/Graphik-Regular-Web.woff2') format('woff2'), + url('https://cdn.xsolla.net/headless-checkout-prod/assets/fonts/graphik/Graphik-Regular-Web.woff') format('woff'), + url('https://cdn.xsolla.net/headless-checkout-prod/assets/fonts/graphik/Graphik-Regular-Web.ttf') format('truetype'); + font-display: swap; +} + +@font-face { + font-weight: 500; + font-style: normal; + font-family: GraphikLCWebUikit; + src: url('https://cdn.xsolla.net/headless-checkout-prod/assets/fonts/graphik/Graphik-Medium-Web.woff2') format('woff2'), + url('https://cdn.xsolla.net/headless-checkout-prod/assets/fonts/graphik/Graphik-Medium-Web.woff') format('woff'), + url('https://cdn.xsolla.net/headless-checkout-prod/assets/fonts/graphik/Graphik-Medium-Web.ttf') format('truetype'); + font-display: swap; +} + +@font-face { + font-weight: bold; + font-style: normal; + font-family: GraphikLCWebUikit; + src: url('https://cdn.xsolla.net/headless-checkout-prod/assets/fonts/graphik/Graphik-Bold-Web.woff2') format('woff2'), + url('https://cdn.xsolla.net/headless-checkout-prod/assets/fonts/graphik/Graphik-Bold-Web.woff') format('woff'), + url('https://cdn.xsolla.net/headless-checkout-prod/assets/fonts/graphik/Graphik-Bold-Web.ttf') format('truetype'); + font-display: swap; +} diff --git a/src/styles/mixins/animation.mixin.scss b/src/styles/mixins/animation.mixin.scss new file mode 100644 index 00000000..30b154f1 --- /dev/null +++ b/src/styles/mixins/animation.mixin.scss @@ -0,0 +1,24 @@ +@mixin loader($color, $size: 25px, $border-width: 2px) { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + + &::before { + content: ''; + display: block; + width: $size; + height: $size; + border: $border-width solid $color; + border-bottom-color: transparent; + border-radius: 100%; + animation: loader 1s linear infinite; + } +} + +@keyframes loader { + 100% { + transform: rotate(360deg); + } +} diff --git a/src/styles/mixins/cart.mixin.scss b/src/styles/mixins/cart.mixin.scss new file mode 100644 index 00000000..2ba8f8f5 --- /dev/null +++ b/src/styles/mixins/cart.mixin.scss @@ -0,0 +1,4 @@ +@mixin line-through { + text-decoration: line-through; + text-decoration-color: var(--psdk-secondary-color); +} diff --git a/src/styles/mixins/controls.mixin.scss b/src/styles/mixins/controls.mixin.scss new file mode 100644 index 00000000..062a78bf --- /dev/null +++ b/src/styles/mixins/controls.mixin.scss @@ -0,0 +1,72 @@ +@mixin psdk-control-base() { + box-sizing: border-box; + height: 40px; + padding: 10px 12px; + border: var(--psdk-input-border-width) solid + var(--psdk-input-default-border-color); + border-radius: var(--psdk-input-radius); + background: var(--psdk-input-bg); + color: var(--psdk-font-color); + font-weight: 400; + font-size: var(--psdk-font-size); + font-family: var(--psdk-font-family); + line-height: var(--psdk-line-height); +} + +@mixin psdk-input() { + @include psdk-control-base; + + &::placeholder { + color: var(--psdk-input-placeholder-color); + } + + &:hover { + border-color: var(--psdk-input-hover-border-color); + + &::placeholder { + color: var(--psdk-input-placeholder-color); + } + } + + &:focus { + border-color: var(--psdk-input-focus-border-color); + + &::placeholder { + color: var(--psdk-input-placeholder-focus-color); + } + } + + &:disabled { + border-color: var(--psdk-input-disabled-border-color); + background: var(--psdk-input-disabled-bg); + } + + &.invalid { + border-color: var(--psdk-input-error-border-color); + } +} + +@mixin psdk-select { + @include psdk-control-base; + + &:hover { + border-color: var(--psdk-input-hover-border-color); + + &::placeholder { + color: var(--psdk-input-placeholder-color); + } + } + + &:focus { + border-color: var(--psdk-input-focus-border-color); + + &::placeholder { + color: var(--psdk-input-placeholder-focus-color); + } + } + + &:disabled { + border-color: var(--psdk-input-disabled-border-color); + background: var(--psdk-input-disabled-bg); + } +} diff --git a/src/styles/mixins/payment-methods.mixin.scss b/src/styles/mixins/payment-methods.mixin.scss new file mode 100644 index 00000000..b578acba --- /dev/null +++ b/src/styles/mixins/payment-methods.mixin.scss @@ -0,0 +1,28 @@ +@mixin payment-methods() { + display: flex; + border: var(--psdk-methods-border-width) solid + var(--psdk-methods-border-color); + border-radius: var(--psdk-methods-radius); + + a { + display: flex; + align-items: center; + box-sizing: border-box; + width: 100%; + height: 48px; + padding: 12px; + color: var(--psdk-font-color); + text-decoration: none; + + .icon { + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + width: 40px; + height: 40px; + margin-right: 12px; + border-radius: 50%; + } + } +} diff --git a/src/styles/mixins/typo.mixin.scss b/src/styles/mixins/typo.mixin.scss new file mode 100644 index 00000000..0fd6b35d --- /dev/null +++ b/src/styles/mixins/typo.mixin.scss @@ -0,0 +1,15 @@ +@mixin psdk-typo() { + color: var(--psdk-font-color); + font-weight: 400; + font-size: var(--psdk-font-size); + font-family: var(--psdk-font-family); + line-height: var(--psdk-line-height); +} + +@mixin psdk-title2 { + color: var(--psdk-title2-color); + font-weight: var(--psdk-title2-font-weight); + font-size: var(--psdk-title2-font-size); + font-family: var(--psdk-font-family); + line-height: var(--psdk-title2-line-height); +} diff --git a/src/styles/style.scss b/src/styles/style.scss new file mode 100644 index 00000000..4c1b2dc4 --- /dev/null +++ b/src/styles/style.scss @@ -0,0 +1,87 @@ +@import './font'; +@import './themes/default/variables'; +@import 'src/styles/mixins/typo.mixin'; + +psdk-submit-button, +psdk-default-submit-button, +psdk-payment-methods, +psdk-saved-methods, +psdk-price-text, +psdk-finance-details, +psdk-legal, +psdk-payment-form, +psdk-status, +psdk-3ds, +psdk-select, +psdk-checkbox, +psdk-user-balance, +psdk-payment-form-messages { + @include psdk-typo; + + .field-error { + color: var(--psdk-alert-color); + } +} + +psdk-text, +psdk-lit-text, +psdk-phone, +psdk-card-number, +psdk-lit-card-number, +psdk-google-pay-button, +psdk-apple-pay { + @include psdk-typo; + + .field-error { + color: var(--psdk-alert-color); + } + + iframe { + width: 100%; + height: 100%; + border: none; + } + + .wrapper { + height: 40px; + margin-bottom: 4px; + } +} + +psdk-google-pay-button { + iframe { + height: 44px; + } +} + +psdk-qr-code { + @include psdk-typo; + + display: flex; + min-height: 170px; + + iframe { + width: 100%; + border: none; + } +} + +.psdk-is-loading { + .wrapper { + border-radius: 5px; + background: #eee; + background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%); + background-size: 200% 100%; + animation: 1s shine linear infinite; + } +} + +@keyframes shine { + to { + background-position-x: -200%; + } +} + +.label { + margin-bottom: 2px; +} diff --git a/src/styles/themes/default/secure-styles/secure-inputs.string-style.scss b/src/styles/themes/default/secure-styles/secure-inputs.string-style.scss new file mode 100644 index 00000000..204ae7be --- /dev/null +++ b/src/styles/themes/default/secure-styles/secure-inputs.string-style.scss @@ -0,0 +1,5 @@ +@use 'src/styles/mixins/controls.mixin' as controls; + +input { + @include controls.psdk-input; +} diff --git a/src/styles/themes/default/secure-styles/style.string-style.scss b/src/styles/themes/default/secure-styles/style.string-style.scss new file mode 100644 index 00000000..bb204452 --- /dev/null +++ b/src/styles/themes/default/secure-styles/style.string-style.scss @@ -0,0 +1,2 @@ +@import './variables.string-style'; +@import './secure-inputs.string-style'; diff --git a/src/styles/themes/default/secure-styles/variables.string-style.scss b/src/styles/themes/default/secure-styles/variables.string-style.scss new file mode 100644 index 00000000..10083aac --- /dev/null +++ b/src/styles/themes/default/secure-styles/variables.string-style.scss @@ -0,0 +1 @@ +@import '../variables'; diff --git a/src/styles/themes/default/variables.scss b/src/styles/themes/default/variables.scss new file mode 100644 index 00000000..7b9583f4 --- /dev/null +++ b/src/styles/themes/default/variables.scss @@ -0,0 +1,72 @@ +:root { + /* stylelint-disable value-keyword-case */ + --psdk-font-family: GraphikLCWebUikit, system-ui, -apple-system, 'Segoe UI', + Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + /* stylelint-enable value-keyword-case */ + + --psdk-neutral-color: rgba(65, 57, 96, 1); + --psdk-success-color: rgba(15, 138, 101, 1); + --psdk-alert-color: rgba(153, 0, 66, 1); + --psdk-warning-color: rgba(204, 65, 0, 1); + --psdk-neutral-bg: rgba(239, 240, 246, 1); + --psdk-success-bg: rgba(232, 252, 239, 1); + --psdk-alert-bg: rgba(255, 229, 232, 1); + --psdk-warning-bg: rgba(255, 245, 229, 1); + --psdk-common-border-radius: 4px; + --psdk-font-size: 14px; + --psdk-font-color: #000; + --psdk-line-height: 18px; + --psdk-secondary-color: rgba(24, 23, 28, 0.5); + --psdk-loader-color: rgb(61, 70, 245); + --psdk-bg-secondary: rgb(255, 255, 255); + + // buttons + --psdk-button-default-bg: rgb(61, 70, 245); + --psdk-button-hover-bg: rgb(13, 13, 242); + --psdk-button-press-bg: rgb(110, 123, 247); + --psdk-button-disabled-bg: rgb(200, 202, 208); + --psdk-button-color: #fff; + --psdk-button-border-radius: var(--psdk-common-border-radius); + + // payment-methods + --psdk-methods-radius: var(--psdk-common-border-radius); + --psdk-methods-border-width: 1px; + --psdk-methods-border-color: rgba(0, 0, 0, 0.15); + + // inputs + --psdk-input-radius: var(--psdk-common-border-radius); + --psdk-input-border-width: 1px; + --psdk-input-bg: rgb(255, 255, 255); + --psdk-input-default-border-color: rgba(0, 0, 0, 0.15); + --psdk-input-hover-border-color: #000; + --psdk-input-focus-border-color: rgb(61, 70, 245); + --psdk-input-error-border-color: rgb(204, 0, 78); + --psdk-input-disabled-border-color: rgba(0, 0, 0, 0.15); + --psdk-input-disabled-bg: rgb(200, 202, 208); + --psdk-input-disabled-color: rgba(0, 0, 0, 0.35); + --psdk-input-placeholder-color: rgba(0, 0, 0, 0.55); + --psdk-input-placeholder-focus-color: rgb(0, 0, 0); + + // checkbox + + --psdk-checkbox-checked-bg: rgba(61, 70, 245, 1); + --psdk-checkbox-unchecked-bg: rgba(65, 57, 96, 0.15); + + // divider + --psdk-divider-color: rgba(0, 0, 0, 0.15); + + // titles + --psdk-title2-color: rgba(0, 0, 0); + --psdk-title2-font-weight: 500; + --psdk-title2-font-size: 14px; + --psdk-title2-line-height: 18px; + + // select-list + --psdk-select-hover-bg: rgba(65, 57, 96, 0.1); + --psdk-select-focus-bg: rgb(61, 70, 245); + --psdk-select-focus-color: rgb(255, 255, 255); + + // shadows + --psdk-shadow: 0px 6px 10px 4px rgba(7, 7, 8, 0.1), + 0px 2px 3px rgba(7, 7, 8, 0.2); +} diff --git a/src/tests/stubs/headless-checkout.mock.ts b/src/tests/stubs/headless-checkout.mock.ts new file mode 100644 index 00000000..c339b02a --- /dev/null +++ b/src/tests/stubs/headless-checkout.mock.ts @@ -0,0 +1,32 @@ +import { Handler } from '../../core/post-messages-client/handler.type'; +import { noopStub } from './noop.stub'; +import { Status } from '../../core/status/status.interface'; + +export class HeadlessCheckoutMock { + public events = { + onCoreEvent: ( + name: string, + handler: Handler, + callback: (value: T | null) => void + ): void => { + this.addEventListener(callback); + }, + }; + + public form = { + onNextAction: noopStub, + }; + private readonly listeners: Array<(value: T | null) => void> = []; + + public addEventListener(callback: (value: T | null) => void): void { + this.listeners.push(callback); + } + + public emitEvent(value: T | null): void { + this.listeners.forEach((callback) => callback(value)); + } + + public async getStatus(): Promise { + return Promise.resolve({} as Status); + } +} diff --git a/src/tests/stubs/noop.stub.ts b/src/tests/stubs/noop.stub.ts new file mode 100644 index 00000000..1f55a755 --- /dev/null +++ b/src/tests/stubs/noop.stub.ts @@ -0,0 +1,3 @@ +export const noopStub = (): void => { + return; +}; diff --git a/src/translations/ar.json b/src/translations/ar.json new file mode 100644 index 00000000..0f64459c --- /dev/null +++ b/src/translations/ar.json @@ -0,0 +1,50 @@ +{ + "ar" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay متاح فقط في متصفح Safari مع تثبيت OS 10.12 Sierra أو IOS 10. يرجى تغيير المتصفح أو تحديد خيار آخر للسداد.", + "apple-pay.errors.payment" : "لم نتمكن من معالجة سدادك. تأكد من تسجيل الدخول إلى Apple على جهازك وقم بإعداد Apple Pay قبل المحاولة مرة أخرى. تستغرق التغييرات في إعدادات Apple دقيقتين للمزامنة.", + "cookie-policy" : "سياسة ملفات تعريف الارتباط", + "disclaimer-with-cookies" : "أنت تؤكد من خلال المتابعة أنك شخص بالغ وفقًا لقوانين ولايتك وتوافق على الالتزام بـ اتفاقية ترخيص المستخدم النهائي الخاصة بإيكسولا<\/a>.
    تجمع إيكسولا بشكل مستقل ملفات تعريف الارتباط الأساسية على هذه الصفحة وكذلك أثناء معالجة عملية الشراء التي تغطيها
    سياسة ملفات تعريف الارتباط الخاصة بإيكسولا <\/a>.
    يُرجى ملاحظة أنه فيما يخص عملية الشراء هذه، فإن إيكسولا هي تاجر معتمد للسجلات ومراقب للبيانات التي ترسلها على هذه الصفحة.
    إذا كانت لديك أية أسئلة، يرجى التواصل مع
    دعم Xsolla Gamer<\/a> .", + "error.title.default" : "تعذرت معالجة السداد", + "finance-details-cart-items-checkout-title" : "الشراء من داخل اللعبة", + "finance-details-hrk-equal" : "يساوي {{value}} كرونا سويدية", + "finance-details-hrk-exchange-rate" : "سعر الصرف: 1 يورو = 7.5345 كرونا سويدية", + "finance-details-sales-tax" : "ضريبة المبيعات", + "finance-details-sales-tax-including" : "شامل ضريبة المبيعات", + "finance-details-sales-tax-percent" : "ضريبة المبيعات {{percent}}%", + "finance-details-subtotal-details-discount" : "خصم", + "finance-details-subtotal-details-fee" : "رسوم نظام الدفع", + "finance-details-subtotal-details-user-balance" : "الرصيد", + "finance-details-subtotal-title" : "الإجمالي الفرعي", + "finance-details-total-title" : "الإجمالي", + "finance-details-transaction-details-invoice" : "المعاملة", + "finance-details-transaction-details-time" : "التاريخ", + "finance-details-vat" : "ضريبة القيمة المضافة", + "finance-details-vat-ghana" : "ضريبة القيمة المضافة، وضريبة التأمين الصحي الوطنية، وضريبة GETFund وضريبة كوفيد-19", + "finance-details-vat-ghana-including" : "متضمنة ضريبة القيمة المضافة، وضريبة التأمين الصحي الوطنية، وضريبة GETFund وضريبة كوفيد-19", + "finance-details-vat-including" : "شامل ضريبة القيمة المضافة", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "شامل GST", + "finance-details-vat-india-percent" : "ضرائب البضائع والخدمات{{percent}}%", + "finance-details-vat-india-percent-including" : "شامل {{percent}}% ضريبة البضائع والخدمات", + "finance-details-vat-percent" : "ضريبة القيمة المضافة{{percent}}%", + "finance-details-vat-percent-including" : "شامل {{percent}}% ضريبة قيمة مضافة", + "hello" : "مرحبًا بالعالم!", + "legal" : "قانوني", + "privacy-policy" : "سياسة الخصوصية", + "refund-policy" : "سياسة الاسترداد", + "saving-status.error.title" : "تعذر حفظ طريقة الدفع", + "saving-status.processing.description" : "في انتظار حفظ طريقة الدفع...", + "saving-status.processing.title" : "حفظ معلومات الدفع", + "saving-status.success.title" : "تم حفظ طريقة الدفع", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "اتصال آمن", + "status.error.title" : "فشلت عملية السداد", + "status.message.auto-cancellation" : "أكمل المعاملة باستخدام طريقة السداد المحددة. إذا اخترت عدم إكمالها في صفحة طريقة الدفع، فأغلق هذه النافذة. لن تُخصم أي مبالغ من حسابك.", + "status.processing.description" : "في انتظار أن يتم السداد...", + "status.processing.title" : "جارٍ تنفيذ السداد", + "status.success.description" : "أرسلنا إيصالك إلى {{email}}", + "status.success.title" : "نجحت عملية السداد" + } + } +} diff --git a/src/translations/bg.json b/src/translations/bg.json new file mode 100644 index 00000000..fa6a5b57 --- /dev/null +++ b/src/translations/bg.json @@ -0,0 +1,50 @@ +{ + "bg" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Услугата Apple Pay е достъпна само в браузър Safari с инсталирана OS 10.12 Sierra или IOS 10. Моля, сменете браузъра си или изберете друг метод на плащане.", + "apple-pay.errors.payment" : "Плащането Ви не може да бъде обработено. Уверете се, че сте вписани в Apple на Вашето устройство и настройте Apple Pay, преди да опитате отново. Синхронизирането на промените в настройките на Apple отнема няколко минути.", + "cookie-policy" : "Политика за бисквитките", + "disclaimer-with-cookies" : "С продължаването Вие потвърждавате, че сте на пълнолетна възраст съгласно законите във Вашата държава и се съгласявате да бъдете обвързан(а) от ЛСКП на Xsolla<\/a>.
    Xsolla самостоятелно събира основни бисквитки на тази страница и по-нататък, докато обработва покупката, която е обхваната от
    Правилата за бисквитки на Xsolla<\/a>.
    Имайте предвид, че за тази покупка Xsolla е упълномощеният регистриран търговец и администратор на данните, които подавате на тази страница.
    Ако имате въпроси, свържете се с
    отдел „Геймърска поддръжка“ на Xsolla<\/a>.", + "error.title.default" : "Неуспешно обработване на плащането", + "finance-details-cart-items-checkout-title" : "Покупка вътре в играта", + "finance-details-hrk-equal" : "Равнява се на {{value}} HRK", + "finance-details-hrk-exchange-rate" : "Обменен курс: 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Данък продажби", + "finance-details-sales-tax-including" : "С включен данък продажби", + "finance-details-sales-tax-percent" : "Данък продажби {{percent}}%", + "finance-details-subtotal-details-discount" : "Отстъпка", + "finance-details-subtotal-details-fee" : "Такса за система за плащане", + "finance-details-subtotal-details-user-balance" : "Баланс", + "finance-details-subtotal-title" : "Междинна сума", + "finance-details-total-title" : "Общо", + "finance-details-transaction-details-invoice" : "Трансакция", + "finance-details-transaction-details-time" : "Дата", + "finance-details-vat" : "ДДС", + "finance-details-vat-ghana" : "ДДС, NHIL, облагане за GETFund и облагане за Covid-19", + "finance-details-vat-ghana-including" : "С включени ДДС, GST, NHIL, облагане за GETFund и облагане за Covid-19", + "finance-details-vat-including" : "Включително ДДС", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Включително GST", + "finance-details-vat-india-percent" : "ДСУ {{percent}}%", + "finance-details-vat-india-percent-including" : "С включен {{percent}}% ДСУ", + "finance-details-vat-percent" : "ДДС {{percent}}%", + "finance-details-vat-percent-including" : "Включително {{percent}}% ДДС", + "hello" : "Здравей, свят!", + "legal" : "Правни документи", + "privacy-policy" : "Политика за поверителност", + "refund-policy" : "Политика за връщане на средства", + "saving-status.error.title" : "Неуспешно запазване на метода на плащане", + "saving-status.processing.description" : "Изчакване за запазване на платежния метод...", + "saving-status.processing.title" : "Запазване на платежната информация", + "saving-status.success.title" : "Методът на плащане беше запазен", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Сигурна връзка", + "status.error.title" : "Неуспешно плащане", + "status.message.auto-cancellation" : "Завършете транзакцията с избрания начин на плащане. Ако изберете да не я завършвате на страницата на начина на плащане, затворете прозореца. Няма да бъдете таксувани.", + "status.processing.description" : "Изчаква завършване на плащане...", + "status.processing.title" : "Плащането се обработва", + "status.success.description" : "Изпратихме касовата Ви бележка на {{email}}", + "status.success.title" : "Успешно плащане" + } + } +} diff --git a/src/translations/cs.json b/src/translations/cs.json new file mode 100644 index 00000000..534cd605 --- /dev/null +++ b/src/translations/cs.json @@ -0,0 +1,50 @@ +{ + "cs" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay je k dispozici pouze v prohlížeči Safari s nainstalovaným macOS 10.12 Sierra nebo iOS 10. Změňte prosím prohlížeč nebo vyberte jinou možnost platby.", + "apple-pay.errors.payment" : "Platbu nelze zpracovat. Než to zkusíte znovu, ujistěte se, že jste na svém zařízení přihlášeni k účtu Apple a máte nastaveno Apple Pay. Synchronizace změn v nastavení Apple trvá několik minut.", + "cookie-policy" : "Zásady používání cookies", + "disclaimer-with-cookies" : "Pokračováním potvrzujete, že jste dospělí podle zákonů Vašeho státu a souhlasíte s tím, že budete vázáni smlouvou EULA společnosti Xsolla<\/a>.
    Společnost Xsolla na této stránce a dále při zpracování nákupu nezávisle shromažďuje základní soubory cookies, na které se vztahují
    Zásady používání cookies společnosti Xsolla<\/a>.
    Upozorňujeme, že pro tento nákup je společnost Xsolla autorizovaným obchodníkem a správcem údajů, které na této stránce odešlete.
    V případě jakýchkoli dotazů se obraťte na
    Podporu hráčů společnosti Xsolla<\/a>.", + "error.title.default" : "Platbu nelze zpracovat", + "finance-details-cart-items-checkout-title" : "Nákup ve hře", + "finance-details-hrk-equal" : "Což odpovídá {{value}} HRK", + "finance-details-hrk-exchange-rate" : "Směnný kurz: 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Daň z obratu", + "finance-details-sales-tax-including" : "Včetně daně z obratu", + "finance-details-sales-tax-percent" : "Daň z obratu {{percent}} %", + "finance-details-subtotal-details-discount" : "Sleva", + "finance-details-subtotal-details-fee" : "Poplatek za platební systém", + "finance-details-subtotal-details-user-balance" : "Zůstatek", + "finance-details-subtotal-title" : "Mezisoučet", + "finance-details-total-title" : "Celkem", + "finance-details-transaction-details-invoice" : "Transakce", + "finance-details-transaction-details-time" : "Datum", + "finance-details-vat" : "DPH", + "finance-details-vat-ghana" : "DPH, NHIL, odvod GETFund a odvod Covid-19", + "finance-details-vat-ghana-including" : "Včetně DPH, NHIL, odvodu GETFund a odvodu Covid-19", + "finance-details-vat-including" : "Včetně DPH", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Včetně GST", + "finance-details-vat-india-percent" : "GST {{percent}} %", + "finance-details-vat-india-percent-including" : "Včetně {{percent}}% GST", + "finance-details-vat-percent" : "DPH {{percent}} %", + "finance-details-vat-percent-including" : "Včetně {{percent}}% DPH", + "hello" : "Ahoj světe!", + "legal" : "Právní informace", + "privacy-policy" : "Zásady ochrany osobních údajů", + "refund-policy" : "Podmínky vrácení peněz", + "saving-status.error.title" : "Způsob platby nelze uložit", + "saving-status.processing.description" : "Čekání na uložení způsobu platby…", + "saving-status.processing.title" : "Uložení informací o platbě", + "saving-status.success.title" : "Způsob platby byl uložen", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Zabezpečené připojení", + "status.error.title" : "Platba se nezdařila", + "status.message.auto-cancellation" : "Dokončete transakci pomocí vybraného způsobu platby. Pokud se na stránce způsobu platby rozhodnete transakci nedokončit, zavřete toto okno. Z Vašeho účtu nebude stržena žádná částka.", + "status.processing.description" : "Čekání na dokončení platby...", + "status.processing.title" : "Zpracovávání platby", + "status.success.description" : "Vaši stvrzenku jsme zaslali na {{email}}", + "status.success.title" : "Platba byla úspěšná" + } + } +} diff --git a/src/translations/de.json b/src/translations/de.json new file mode 100644 index 00000000..0fa8d13e --- /dev/null +++ b/src/translations/de.json @@ -0,0 +1,50 @@ +{ + "de" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay ist nur im Safari-Browser mit installiertem macOS Sierra 10.12 oder iOS 10 verfügbar. Wechsle zum Safari-Browser oder wähle eine andere Zahlungsoption aus.", + "apple-pay.errors.payment" : "Zahlung konnte nicht verarbeitet werden. Vergewissere dich, ob du auf deinem Gerät bei Apple angemeldet bist und Apple Pay eingerichtet hast, bevor du es erneut versuchst. Es dauert einige Minuten, bis Änderungen in den Apple-Einstellungen synchronisiert sind.", + "cookie-policy" : "Cookie-Richtlinie", + "disclaimer-with-cookies" : "Indem du fortfährst, bestätigst du, dass du gemäß den Gesetzen deines Landes volljährig bist und dich mit Xsollas EULA<\/a> einverstanden erklärst.
    Xsolla nutzt auf dieser Seite und im weiteren Verlauf des Bezahlvorgangs selbständig wesentliche Cookies, die Nutzung erfolgt im Einklang mit
    Xsollas Cookie-Richtlinie<\/a>.
    Für diesen Kauf fungiert Xsolla als autorisierter Merchant of Record und Verantwortlicher für die Daten, die du auf dieser Seite angibst.
    Bitte wende dich bei Fragen an den
    Xsolla-Support für Gamer<\/a>.", + "error.title.default" : "Zahlung konnte nicht verarbeitet werden", + "finance-details-cart-items-checkout-title" : "Ingame-Kauf", + "finance-details-hrk-equal" : "Entspricht {{value}} HRK", + "finance-details-hrk-exchange-rate" : "Wechselkurs: 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Sales Tax", + "finance-details-sales-tax-including" : "Inklusive Sales Tax", + "finance-details-sales-tax-percent" : "{{percent}} % Umsatzsteuer", + "finance-details-subtotal-details-discount" : "Rabatt", + "finance-details-subtotal-details-fee" : "Zahlungssystemgebühr", + "finance-details-subtotal-details-user-balance" : "Guthaben", + "finance-details-subtotal-title" : "Zwischensumme", + "finance-details-total-title" : "Summe", + "finance-details-transaction-details-invoice" : "Transaktion", + "finance-details-transaction-details-time" : "Datum", + "finance-details-vat" : "MwSt.", + "finance-details-vat-ghana" : "VAT, NHIL, GETFund Levy und Covid-19 Levy", + "finance-details-vat-ghana-including" : "Inklusive VAT, NHIL, GETFund Levy und Covid-19 Levy", + "finance-details-vat-including" : "Inklusive MwSt.", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Inklusive GST", + "finance-details-vat-india-percent" : "{{percent}} % GST", + "finance-details-vat-india-percent-including" : "Inklusive {{percent}} % GST", + "finance-details-vat-percent" : "{{percent}} % MwSt.", + "finance-details-vat-percent-including" : "Einschließlich {{percent}} % MWSt.", + "hello" : "Hallo Welt!", + "legal" : "Rechtliches", + "privacy-policy" : "Datenschutzerklärung", + "refund-policy" : "Erstattungsrichtlinie", + "saving-status.error.title" : "Zahlungsmethode konnte nicht gespeichert werden", + "saving-status.processing.description" : "Zahlungsmethode wird gespeichert …", + "saving-status.processing.title" : "Zahlungsdaten werden gespeichert", + "saving-status.success.title" : "Zahlungsmethode gespeichert", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Sichere Verbindung", + "status.error.title" : "Zahlung fehlgeschlagen", + "status.message.auto-cancellation" : "Schließe die Transaktion mit der ausgewählten Zahlungsmethode ab. Wenn du die Transaktion auf der Seite der Zahlungsmethoden nicht abschließen möchtest, schließe dieses Fenster. Es wird dann kein Geld abgebucht.", + "status.processing.description" : "Zahlung wird durchgeführt …", + "status.processing.title" : "Zahlung wird verarbeitet", + "status.success.description" : "Wir haben den Beleg an {{email}} gesendet", + "status.success.title" : "Zahlung erfolgreich" + } + } +} diff --git a/src/translations/en.json b/src/translations/en.json new file mode 100644 index 00000000..6b46d57c --- /dev/null +++ b/src/translations/en.json @@ -0,0 +1,50 @@ +{ + "en": { + "translation": { + "hello": "Hello world!", + "legal": "Legal", + "disclaimer-with-cookies": "By proceeding, you confirm you’re an adult according to your state laws and agree to be bound by Xsolla’s EULA.
    Xsolla independently collects essential cookies on this page and further while processing the purchase which is covered by Xsolla’s Cookie Policy.
    Please note that for this purchase, Xsolla is the authorized merchant of record and controller of the data you submit on this page.
    If you have any questions, please reach out to Xsolla Gamer Support.", + "cookie-policy": "Cookie policy", + "privacy-policy": "Privacy policy", + "refund-policy": "Refund policy", + "sctl-indications": "SCTL Indications", + "secure-connection": "Secure connection", + "status.processing.title": "Processing payment", + "status.processing.description": "Waiting for payment to complete...", + "status.error.title": "Payment failed", + "status.success.title": "Payment successful", + "status.success.description": "We sent your receipt to {{email}}", + "status.message.auto-cancellation": "Complete the transaction with the selected payment method. If you choose not to complete it on the payment method's page, close this window. Your account won't be charged.", + "finance-details-total-title": "Total", + "finance-details-subtotal-title": "Subtotal", + "finance-details-hrk-equal": "Equals {{value}} HRK", + "finance-details-hrk-exchange-rate": "Exchange rate: 1 EUR = 7.5345 HRK", + "finance-details-transaction-details-invoice": "Transaction", + "finance-details-transaction-details-time": "Date", + "finance-details-vat": "VAT", + "finance-details-vat-percent": "VAT {{percent}}%", + "finance-details-vat-including": "Including VAT", + "finance-details-vat-percent-including": "Including {{percent}}% VAT", + "finance-details-vat-india": "GST", + "finance-details-vat-india-percent": "GST {{percent}}%", + "finance-details-vat-india-including": "Including GST", + "finance-details-vat-india-percent-including": "Including {{percent}}% GST", + "finance-details-vat-ghana": "VAT, NHIL, GETFund Levy, and Covid-19 Levy", + "finance-details-vat-ghana-including": "Including VAT, NHIL, GETFund Levy, and Covid-19 Levy", + "finance-details-sales-tax": "Sales tax", + "finance-details-sales-tax-percent": "Sales tax {{percent}}%", + "finance-details-sales-tax-including": "Sales tax included", + "finance-details-subtotal-details-fee": "Payment system fee", + "finance-details-subtotal-details-discount": "Discount", + "finance-details-subtotal-details-user-balance": "Balance", + "finance-details-cart-items-checkout-title": "In-game purchase", + "saving-status.processing.title": "Saving payment information", + "saving-status.processing.description": "Waiting for payment method to save...", + "saving-status.error.title": "Couldn't save payment method", + "saving-status.success.title": "Payment method saved", + "apple-pay.errors.payment": "Unable to process your payment. Make sure you’re signed in to Apple on your device and set up Apple Pay before you try again. Changes in Apple settings take a couple of minutes to sync.", + "apple-pay.errors.device-is-not-capable": "Apple Pay is available only in Safari browser with installed OS 10.12 Sierra or IOS 10. Please change your browser or select another payment option.", + "error.title.default": "Couldn't process payment" + } + } +} diff --git a/src/translations/es.json b/src/translations/es.json new file mode 100644 index 00000000..da3eca61 --- /dev/null +++ b/src/translations/es.json @@ -0,0 +1,50 @@ +{ + "es" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay solo está disponible en el navegador Safari con el SO Sierra 10.12 o IOS 10 instalado. Por favor, cambia tu navegador o selecciona otra opción de pago.", + "apple-pay.errors.payment" : "No hemos podido procesar el pago. Asegúrate de haber iniciado sesión en Apple en tu dispositivo y de tener configurado Apple Pay antes de volver a intentarlo. Los cambios en la configuración de Apple tardan unos minutos en actualizarse.", + "cookie-policy" : "Política de cookies", + "disclaimer-with-cookies" : "Al continuar, confirmas que eres mayor de edad según las leyes de tu estado y aceptas el Acuerdo de licencia de usuario final de Xsolla<\/a>.
    Xsolla recopila de forma independiente las cookies esenciales en esta página y posteriormente al procesar la compra conforme a la
    Política de cookies de Xsolla<\/a>.
    Te informamos de que para esta compra, Xsolla es el comerciante titular y responsable autorizado de los datos que facilites en esta página.
    Si tienes alguna pregunta, contacta con el
    servicio de asistencia al jugador<\/a>.", + "error.title.default" : "No se pudo procesar el pago", + "finance-details-cart-items-checkout-title" : "Compra en el juego", + "finance-details-hrk-equal" : "Es igual a {{value}} HRK", + "finance-details-hrk-exchange-rate" : "Tipo de cambio: 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Impuesto sobre las ventas", + "finance-details-sales-tax-including" : "Impuesto sobre las ventas incluido", + "finance-details-sales-tax-percent" : "Impuesto sobre las ventas {{percent}} %", + "finance-details-subtotal-details-discount" : "Descuento", + "finance-details-subtotal-details-fee" : "Comisión del sistema de pago", + "finance-details-subtotal-details-user-balance" : "Saldo", + "finance-details-subtotal-title" : "Subtotal", + "finance-details-total-title" : "Total", + "finance-details-transaction-details-invoice" : "Transacción", + "finance-details-transaction-details-time" : "Fecha", + "finance-details-vat" : "IVA", + "finance-details-vat-ghana" : "IVA, NHIL y los impuestos GETFund y Covid-19", + "finance-details-vat-ghana-including" : "Incluyendo el IVA, GST, NHIL y los impuestos GETFund y Covid-19", + "finance-details-vat-including" : "Incluye IVA", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Incluyendo GST", + "finance-details-vat-india-percent" : "GST {{percent}} %", + "finance-details-vat-india-percent-including" : "{{percent}} % GST incluido​", + "finance-details-vat-percent" : "IVA {{percent}} %", + "finance-details-vat-percent-including" : "{{percent}} % IVA incluido​", + "hello" : "¡Hola mundo!", + "legal" : "Legal", + "privacy-policy" : "Política de privacidad", + "refund-policy" : "Política de reembolso", + "saving-status.error.title" : "No se pudo guardar método de pago", + "saving-status.processing.description" : " Esperando que se guarde el método de pago...", + "saving-status.processing.title" : "Guardando la información de pago", + "saving-status.success.title" : "Método de pago guardado", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Conexión segura", + "status.error.title" : "Pago fallido", + "status.message.auto-cancellation" : "Completa la transacción con el método de pago seleccionado. Si decides no completarla en la página del método de pago, cierra esta ventana. No se realizará ningún cargo en tu cuenta.", + "status.processing.description" : "Esperando la confirmación del pago...", + "status.processing.title" : "Procesando el pago", + "status.success.description" : "Hemos enviado el recibo a {{email}}", + "status.success.title" : "Pago efectuado" + } + } +} diff --git a/src/translations/fr.json b/src/translations/fr.json new file mode 100644 index 00000000..ee449c5f --- /dev/null +++ b/src/translations/fr.json @@ -0,0 +1,50 @@ +{ + "fr" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay est disponible uniquement dans le navigateur Safari avec l'OS 10.12 Sierra ou IOS 10 installé. Veuillez changer de navigateur ou sélectionner une autre option de paiement.", + "apple-pay.errors.payment" : "Impossible de traiter votre paiement. Vérifiez que vous êtes connecté à Apple sur votre appareil et que vous avez configuré Apple Pay avant de réessayer. La synchronisation des modifications apportées aux paramètres Apple prend quelques minutes.", + "cookie-policy" : "Politique en matière de cookies", + "disclaimer-with-cookies" : "En procédant, vous confirmez que vous êtes un adulte au sens de la législation de votre État et que vous acceptez d'être lié par le CLUF de Xsolla<\/a>.
    Xsolla collecte indépendamment des cookies essentiels sur cette page et plus loin pendant le traitement de l'achat qui est couvert par
    la politique de Xsolla en matière de cookies<\/a>.
    Veuillez noter que pour cet achat, Xsolla est le marchand autorisé et le contrôleur des données que vous soumettez sur cette page.
    Si vous avez des questions, veuillez contacter
    l'Assistance aux joueurs Xsolla<\/a>.", + "error.title.default" : "Impossible de traiter le paiement", + "finance-details-cart-items-checkout-title" : "Achat intégré", + "finance-details-hrk-equal" : "Égal {{value}} HRK", + "finance-details-hrk-exchange-rate" : "Taux de change : 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Taxe de vente", + "finance-details-sales-tax-including" : "Taxe de vente incluse", + "finance-details-sales-tax-percent" : "Taxe de vente de {{percent}} %", + "finance-details-subtotal-details-discount" : "Remise", + "finance-details-subtotal-details-fee" : "Frais du système de paiement", + "finance-details-subtotal-details-user-balance" : "Solde", + "finance-details-subtotal-title" : "Sous-total", + "finance-details-total-title" : "Total", + "finance-details-transaction-details-invoice" : "Transaction", + "finance-details-transaction-details-time" : "Date", + "finance-details-vat" : "TVA", + "finance-details-vat-ghana" : "La TVA, la NHIL, le prélèvement GETFund et le prélèvement Covid-19", + "finance-details-vat-ghana-including" : "Y compris la TVA, la NHIL, le prélèvement GETFund et le prélèvement Covid-19", + "finance-details-vat-including" : "TVA incluse", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "GST incluse", + "finance-details-vat-india-percent" : "TPS de {{percent}} %", + "finance-details-vat-india-percent-including" : "TPS de {{percent}} % incluse", + "finance-details-vat-percent" : "TVA de {{percent}} %", + "finance-details-vat-percent-including" : "TVA de {{percent}} % incluse", + "hello" : "Bonjour monde!", + "legal" : "Juridique", + "privacy-policy" : "Politique de confidentialité", + "refund-policy" : "Politique de remboursement", + "saving-status.error.title" : "Impossible d'enregistrer le mode de paiement", + "saving-status.processing.description" : "En attente du mode de paiement à enregistrer…", + "saving-status.processing.title" : "‌ Enregistrement des informations de paiement", + "saving-status.success.title" : "Mode de paiement enregistré", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Connexion sécurisée", + "status.error.title" : "Échec de paiement", + "status.message.auto-cancellation" : "Complétez la transaction avec le mode de paiement sélectionné. Si vous choisissez de ne pas terminer la transaction une fois sur la page du mode de paiement, fermez la fenêtre. Votre compte ne sera pas débité.", + "status.processing.description" : "En attente de l'achèvement du paiement…", + "status.processing.title" : "Traitement du paiement", + "status.success.description" : "Nous avons envoyé votre reçu à {{email}}", + "status.success.title" : "Paiement réussi" + } + } +} diff --git a/src/translations/he.json b/src/translations/he.json new file mode 100644 index 00000000..14c25ceb --- /dev/null +++ b/src/translations/he.json @@ -0,0 +1,50 @@ +{ + "he" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay זמין רק בדפדפן Safari עם מערכת הפעלה OS 10.12 Sierra או IOS 10. צריך להשתמש בדפדפן אחר או לבחור אפשרות תשלום אחרת.", + "apple-pay.errors.payment" : "עיבוד התשלום נכשל. לפני שמנסים שוב, חשוב לוודא שנכנסת ל-Apple במכשיר שלך והגדרת את Apple Pay. סנכרון השינויים שמבוצעים ב-Apple יימשך כמה דקות.", + "cookie-policy" : "מדיניות בנושא קובצי Cookie", + "disclaimer-with-cookies" : "המשך מבטא אישור שלך שגילך נחשב מבוגר לפי החוקים במדינה שלך, והסכמה להסכם הרישיון של Xsolla למשתמש קצה<\/a>.
    ‏בדף זה, Xsolla אוספת באופן עצמאי קובצי Cookie מהותיים בזמן עיבוד הרכישה, כפי שמוצהר ב
    מדיניות Xsolla לקובצי Cookie<\/a>.
    לידיעתך, למטרות הרכישה הנוכחית, Xsolla היא המוכרת הרשומה המורשית ומבקרת הנתונים שנשלחים על ידך בדף הזה.
    בכל שאלה אפשר לפנות אל
    התמיכה של Xsolla Gamer<\/a>.", + "error.title.default" : "העיבוד של התשלום נכשל", + "finance-details-cart-items-checkout-title" : "רכישה מתוך המשחק", + "finance-details-hrk-equal" : "שווה-ערך ל-{{value}} קונה קרואטי", + "finance-details-hrk-exchange-rate" : "שער המרה: 1 אירו = 7.5345 קונה קרואטי", + "finance-details-sales-tax" : "מס מכירה", + "finance-details-sales-tax-including" : "כולל מס מכירה", + "finance-details-sales-tax-percent" : "מס מכירה {{percent}}%", + "finance-details-subtotal-details-discount" : "הנחה", + "finance-details-subtotal-details-fee" : "עמלת מערכת תשלומים", + "finance-details-subtotal-details-user-balance" : "יתרה", + "finance-details-subtotal-title" : "סיכום ביניים", + "finance-details-total-title" : "סה\"כ", + "finance-details-transaction-details-invoice" : "עסקה", + "finance-details-transaction-details-time" : "תאריך", + "finance-details-vat" : "מע\"מ", + "finance-details-vat-ghana" : "מע\"מ,‏ NHIL, מס GETFund ומס Covid-19", + "finance-details-vat-ghana-including" : "כולל מע\"מ,‏ NHIL, מס GETFund ומס Covid-19", + "finance-details-vat-including" : "כולל מע\"מ", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "כולל GST", + "finance-details-vat-india-percent" : "מס סחורות ושירותים {{percent}}%", + "finance-details-vat-india-percent-including" : "כולל {{percent}}% מס סחורות ושירותים", + "finance-details-vat-percent" : "מע\"מ {{percent}}%", + "finance-details-vat-percent-including" : "כולל {{percent}}% מע\"מ", + "hello" : "שלום עולם!", + "legal" : "משפטי", + "privacy-policy" : "מדיניות פרטיות", + "refund-policy" : "מדיניות החזרים כספיים", + "saving-status.error.title" : "השמירה של אמצעי התשלום נכשלה", + "saving-status.processing.description" : "מחכה לאמצעי תשלום לשמור...", + "saving-status.processing.title" : "שמירת אמצעי התשלום מתבצעת", + "saving-status.success.title" : "אמצעי התשלום נשמר", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "חיבור מאובטח", + "status.error.title" : "התשלום נכשל", + "status.message.auto-cancellation" : "צריך להשלים את העסקה באמצעות אמצעי התשלום שבחרת. אם אינך רוצה להשלים את העסקה בדף של אמצעי התשלום, אפשר לסגור את החלון והחשבון שלך לא יחויב.", + "status.processing.description" : "בהמתנה לסיום התשלום...", + "status.processing.title" : "התשלום בעיבוד", + "status.success.description" : "שלחנו את הקבלה אל {{email}}", + "status.success.title" : "התשלום התקבל" + } + } +} diff --git a/src/translations/it.json b/src/translations/it.json new file mode 100644 index 00000000..c7697e16 --- /dev/null +++ b/src/translations/it.json @@ -0,0 +1,50 @@ +{ + "it" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay è disponibile solo sul browser Safari con OS 10.12 Sierra o IOS 10. Cambia il browser o seleziona un altro metodo di pagamento.", + "apple-pay.errors.payment" : "Impossibile processare il pagamento. Verifica di avere eseguito l'accesso a Apple sul dispositivo e configura Apple Pay prima di provare di nuovo. Sono necessari un paio di minuti per la sincronizzazione delle modifiche alle impostazioni di Apple.", + "cookie-policy" : "Informativa sui Cookie", + "disclaimer-with-cookies" : "Proseguendo, confermi di essere un adulto secondo le leggi del tuo stato e acconsenti di essere vincolato dall'EULA di Xsolla<\/a>.
    Xsolla raccoglie autonomamente i cookie essenziali su questa pagina e in seguito durante l'elaborazione dell'acquisto, che è coperto dalla
    Politica sui cookie di Xsolla<\/a>.
    Si prega di notare che per questo acquisto, Xsolla è il rivenditore autorizzato e il responsabile del trattamento dei dati inviati in questa pagina.
    Per eventuali domande, si prega di contattare l'
    Assistenza Gamer Xsolla<\/a>.", + "error.title.default" : "Non è stato possibile elaborare il pagamento", + "finance-details-cart-items-checkout-title" : "Acquisto in-game", + "finance-details-hrk-equal" : "Pari a {{value}} HKR", + "finance-details-hrk-exchange-rate" : "Tasso di cambio: 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Imposta sulle vendite", + "finance-details-sales-tax-including" : "Imposta sulle vendite", + "finance-details-sales-tax-percent" : "Imposta sulle vendite {{percent}}%", + "finance-details-subtotal-details-discount" : "Sconto", + "finance-details-subtotal-details-fee" : "Commissione sistema di pagamento", + "finance-details-subtotal-details-user-balance" : "Saldo", + "finance-details-subtotal-title" : "Subtotale", + "finance-details-total-title" : "Totale", + "finance-details-transaction-details-invoice" : "Transazione", + "finance-details-transaction-details-time" : "Data", + "finance-details-vat" : "IVA", + "finance-details-vat-ghana" : "IVA, NHIL, contributo GETFund e contributo Covid-19", + "finance-details-vat-ghana-including" : "Inclusi IVA, NHIL, contributo GETFund e contributo Covid-19", + "finance-details-vat-including" : "Inclusa IVA", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Inclusa GST", + "finance-details-vat-india-percent" : "GST {{percent}}%", + "finance-details-vat-india-percent-including" : "Compresa GST {{percent}}% ", + "finance-details-vat-percent" : "IVA {{percent}}%", + "finance-details-vat-percent-including" : "Include {{percent}}% IVA", + "hello" : "Ciao mondo!", + "legal" : "Note legali", + "privacy-policy" : "Informativa sulla Privacy", + "refund-policy" : "Politica di Rimborso", + "saving-status.error.title" : "Non è stato possibile salvare il metodo di pagamento", + "saving-status.processing.description" : "In attesa di salvare il metodo di pagamento...", + "saving-status.processing.title" : "Salvataggio delle informazioni di pagamento", + "saving-status.success.title" : "Metodo di pagamento salvato", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Connessione sicura", + "status.error.title" : "Pagamento non riuscito", + "status.message.auto-cancellation" : "Completa la transazione con il metodo di pagamento selezionato. Se decidi di non completarlo sulla pagina del metodo di pagamento, chiudi questa finestra. Non verrà addebitato niente sul tuo conto.", + "status.processing.description" : "In attesa che il pagamento venga completato…", + "status.processing.title" : "Elaborazione pagamento", + "status.success.description" : "Abbiamo inviato una ricevuta a {{email}}", + "status.success.title" : "Pagamento effettuato con successo" + } + } +} diff --git a/src/translations/ja.json b/src/translations/ja.json new file mode 100644 index 00000000..e05e9b81 --- /dev/null +++ b/src/translations/ja.json @@ -0,0 +1,50 @@ +{ + "ja" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Payは、OS 10.12 SierraまたはIOS 10をインストールしたSafariブラウザでのみご利用いただけます。ブラウザを変更するか、他の決済方法を選択してください。", + "apple-pay.errors.payment" : "トラザックションを処理できません。お使いのデバイスでAppleにサインインし、Apple Payを設定していることを確認してから再試行してください。Appleの設定の変更は、同期に2〜3分かかります。", + "cookie-policy" : "クッキーポリシー", + "disclaimer-with-cookies" : "以下の手続きにより、利用者はお住まいの州法に基づいて成人であることを確認し、エクソーラEULA<\/a>に拘束されることに同意します。
    エクソーラは、このページ上およびさらに購入処理中に、
    エクソーラクッキーポリシー<\/a>の対象となる必須のクッキを独自に収集します。
    この購入に関して、Xsollaは認証されたマーチャントであり、このページで送信されたデータの管理者であることにご注意ください。
    ご質問がある場合は、
    エクソーラゲーマーサポート<\/a>までお問い合わせください。", + "error.title.default" : "決済を処理できません", + "finance-details-cart-items-checkout-title" : "ゲーム内購入", + "finance-details-hrk-equal" : "{{value}} HRKに相当する", + "finance-details-hrk-exchange-rate" : "為替レート:1ユーロ=7.5345HRK", + "finance-details-sales-tax" : "消費税", + "finance-details-sales-tax-including" : "消費税込み", + "finance-details-sales-tax-percent" : "売上税 {{percent}}%", + "finance-details-subtotal-details-discount" : "割引", + "finance-details-subtotal-details-fee" : "決済システム料金", + "finance-details-subtotal-details-user-balance" : "残高", + "finance-details-subtotal-title" : "小計", + "finance-details-total-title" : "合計", + "finance-details-transaction-details-invoice" : "取引", + "finance-details-transaction-details-time" : "日付", + "finance-details-vat" : "消費税", + "finance-details-vat-ghana" : "VAT、NHIL、GETFund税及びCovid-19税", + "finance-details-vat-ghana-including" : "VAT、NHIL、GETFund税及びCovid-19税を含む", + "finance-details-vat-including" : "消費税込み", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "GST込み", + "finance-details-vat-india-percent" : "財・サービス税 {{percent}}%", + "finance-details-vat-india-percent-including" : "{{percent}}% 商品サービス税込み", + "finance-details-vat-percent" : "消費税 {{percent}}%", + "finance-details-vat-percent-including" : "{{percent}}% 消費税込み", + "hello" : "こんにちは、世界!", + "legal" : "法的文書", + "privacy-policy" : "プライバシーポリシー", + "refund-policy" : "返金ポリシー", + "saving-status.error.title" : "決済方法を保存できません", + "saving-status.processing.description" : "決済方法の保存を待っています...", + "saving-status.processing.title" : " 決済情報の保存 ", + "saving-status.success.title" : "決済方法を保存しました", + "sctl-indications" : "特商法取引に基づく表示", + "secure-connection" : "安全な接続", + "status.error.title" : "決済失敗", + "status.message.auto-cancellation" : "選択された決済方法で取引を完了してください。決済方法のページで完了しないことを選択した場合は、このウィンドウを閉じてください。アカウントには請求されません。", + "status.processing.description" : "決済の完了待ち...", + "status.processing.title" : "決済処理中", + "status.success.description" : "領収書を{{email}}に送信しました", + "status.success.title" : "決済完了" + } + } +} diff --git a/src/translations/ko.json b/src/translations/ko.json new file mode 100644 index 00000000..4d7aefd4 --- /dev/null +++ b/src/translations/ko.json @@ -0,0 +1,50 @@ +{ + "ko" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay는 Sierra 10.12 또는 IOS 10 OS가 설치된 Safari 브라우저에서만 이용 가능합니다. 브라우저를 변경하거나 다른 결제 옵션을 선택해 주세요.", + "apple-pay.errors.payment" : "결제를 처리할 수 없습니다. 귀하의 장치에서 Apple에 로그인했는지 확인하고 Apple Pay를 설정한 후 다시 시도하세요. Apple 설정의 변경 사항을 동기화하는 데에는 몇 분 정도 걸립니다.", + "cookie-policy" : "쿠키 정책", + "disclaimer-with-cookies" : "계속 진행하면 해당 주의 법에 따라 성인임을 확인하고 엑솔라 EULA<\/a>에 동의하게 됩니다.
    엑솔라는 구매를 처리하는 동안
    엑솔라 쿠키 정책<\/a>의 적용을 받는 필수 쿠키를 이 페이지에서 독립적으로 수집합니다.
    이 구매의 경우 엑솔라는 이 페이지에서 제출된 데이터의 공인 기록 및 관리자임을 유의해 주십시오.
    문의 사항이 있으시면
    엑솔라 게이머 지원<\/a>으로 문의해주시기 바랍니다.", + "error.title.default" : "결제를 처리할 수 없음", + "finance-details-cart-items-checkout-title" : "인게임 구매", + "finance-details-hrk-equal" : "= {{value}} HRK", + "finance-details-hrk-exchange-rate" : "환율: 1 EUR = 7.5345 HRK", + "finance-details-sales-tax" : "판매 세금", + "finance-details-sales-tax-including" : "판매 세금 포함됨", + "finance-details-sales-tax-percent" : "판매 세금 {{percent}}%", + "finance-details-subtotal-details-discount" : "할인", + "finance-details-subtotal-details-fee" : "결제 시스템 수수료", + "finance-details-subtotal-details-user-balance" : "잔액", + "finance-details-subtotal-title" : "소계", + "finance-details-total-title" : "합계", + "finance-details-transaction-details-invoice" : "거래", + "finance-details-transaction-details-time" : "날짜", + "finance-details-vat" : "부가가치세", + "finance-details-vat-ghana" : "VAT, NHIL, GETFund Levy 및 Covid-19 Levy", + "finance-details-vat-ghana-including" : "VAT, NHIL, GETFund Levy 및 Covid-19 Levy 등 포함", + "finance-details-vat-including" : "부가세 포함", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "GST 포함", + "finance-details-vat-india-percent" : "GST {{percent}}%", + "finance-details-vat-india-percent-including" : "{{percent}}% GST 포함", + "finance-details-vat-percent" : "VAT {{percent}}%", + "finance-details-vat-percent-including" : "{{percent}}% VAT 포함", + "hello" : "안녕하세요 세상!", + "legal" : "법률", + "privacy-policy" : "개인정보 보호정책", + "refund-policy" : "환불 정책", + "saving-status.error.title" : "결제 방법을 저장할 수 없음", + "saving-status.processing.description" : "결제 방법 저장을 기다리는 중...", + "saving-status.processing.title" : "결제 정보 저장중", + "saving-status.success.title" : "결제 방법 저장됨", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "보안 연결", + "status.error.title" : "결제 실패함", + "status.message.auto-cancellation" : "선택한 결제 방식으로 거래를 완료합니다. 결제 방식 페이지에서 완료하지 않기로 선택한 경우 이 창을 닫습니다. 계정으로 요금이 청구되지 않습니다.", + "status.processing.description" : "결제 완료 대기 중...", + "status.processing.title" : "결제 처리 중", + "status.success.description" : "{{email}}(으)로 영수증을 보내드렸습니다.", + "status.success.title" : "결제 성공함" + } + } +} diff --git a/src/translations/pl.json b/src/translations/pl.json new file mode 100644 index 00000000..0eccc535 --- /dev/null +++ b/src/translations/pl.json @@ -0,0 +1,50 @@ +{ + "pl" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Usługa Apple Pay dostępna jest wyłącznie w przeglądarce Safari z zainstalowanym systemem OS 10.12 Sierra lub iOS 10. Zmień przeglądarkę lub wybierz inną metodę płatności.", + "apple-pay.errors.payment" : "Nie można przetworzyć Twojej płatności. Upewnij się, że jesteś zalogowany w Apple na swoim urządzeniu i skonfiguruj Apple Pay przed ponowną próbą. Synchronizacja zmian w ustawieniach Apple może potrwać kilka minut.", + "cookie-policy" : "Polityka plików cookie", + "disclaimer-with-cookies" : "Kontynuując, potwierdzasz, że jesteś osobą pełnoletnią w rozumieniu przepisów krajowych i zgadzasz się przestrzegać postanowień Umowy licencyjnej użytkownika końcowego Xsolla<\/a>.
    Xsolla stosuje niezależnie niezbędne pliki cookie na tej stronie, a także podczas obsługi płatności, zgodnie z postanowieniami
    Polityki plików cookie Xsolla<\/a>.
    Przypominamy, że w zakresie niniejszego zakupu Xsolla pełni rolę upoważnionego, ostatecznego sprzedawcy i administratora danych przekazywanych przez Ciebie za pośrednictwem strony.
    W razie pytań prosimy o kontakt z
    Działem wsparcia graczy Xsolla<\/a>.", + "error.title.default" : "Nie udało się przetworzyć płatności", + "finance-details-cart-items-checkout-title" : "Zakup w grze", + "finance-details-hrk-equal" : "Wynosi {{value}} HRK", + "finance-details-hrk-exchange-rate" : "Kurs wymiany: 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Podatek od sprzedaży", + "finance-details-sales-tax-including" : "Kwota zawiera podatek od sprzedaży", + "finance-details-sales-tax-percent" : "Podatek od sprzedaży {{percent}}%", + "finance-details-subtotal-details-discount" : "Rabat", + "finance-details-subtotal-details-fee" : "Prowizja systemu płatniczego", + "finance-details-subtotal-details-user-balance" : "Saldo", + "finance-details-subtotal-title" : "Suma częściowa", + "finance-details-total-title" : "Suma zamówienia", + "finance-details-transaction-details-invoice" : "Transakcja", + "finance-details-transaction-details-time" : "Data", + "finance-details-vat" : "VAT", + "finance-details-vat-ghana" : "VAT, NHIL, opłaty GETFund i Covid-19", + "finance-details-vat-ghana-including" : "Zawiera VAT, NHIL, opłaty GETFund i Covid-19", + "finance-details-vat-including" : "Zawiera VAT", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Zawiera GST", + "finance-details-vat-india-percent" : "GST {{percent}}%", + "finance-details-vat-india-percent-including" : "Zawiera {{percent}}% GST", + "finance-details-vat-percent" : "VAT {{percent}}%", + "finance-details-vat-percent-including" : "Zawiera {{percent}}% VAT", + "hello" : "Witaj świecie!", + "legal" : "Informacje prawne", + "privacy-policy" : "Polityka prywatności", + "refund-policy" : "Polityka zwrotów", + "saving-status.error.title" : "Nie udało się zapisać metody płatności", + "saving-status.processing.description" : " Oczekiwanie na zapisanie metody płatności...", + "saving-status.processing.title" : "Zapisywanie danych płatności", + "saving-status.success.title" : "Zapisano metodę płatności", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Bezpieczne połączenie", + "status.error.title" : "Płatność nieudana", + "status.message.auto-cancellation" : "Dokończ transakcję przy użyciu wybranej metody płatności. Jeśli nie chcesz zrealizować transakcji na stronie metody płatności, zamknij to okno. Twoje konto nie zostanie obciążone.", + "status.processing.description" : "Oczekiwanie na sfinalizowanie płatności...", + "status.processing.title" : "Trwa przetwarzanie płatności", + "status.success.description" : "Wysłaliśmy rachunek na adres {{email}}", + "status.success.title" : "Płatność zrealizowana" + } + } +} diff --git a/src/translations/pt.json b/src/translations/pt.json new file mode 100644 index 00000000..6ca2d052 --- /dev/null +++ b/src/translations/pt.json @@ -0,0 +1,50 @@ +{ + "pt" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay só está disponível no navegador Safari com o OS 10.12 Sierra ou IOS 10. Troque de navegador ou selecione outra opção de pagamento.", + "apple-pay.errors.payment" : "Não foi possível processar seu pagamento. Certifique-se de estar acessando sua conta Apple em seu dispositivo e configure a Apple Pay antes de tentar novamente. As mudanças nas configurações da Apple pode levar alguns minutos para ser sincronizada.", + "cookie-policy" : "Política de Cookies", + "disclaimer-with-cookies" : "Ao prosseguir, você confirma que é um adulto de acordo com as leis do seu estado e concorda em se vincular ao CLUA da Xsolla<\/a>.
    A Xsolla coleta cookies essenciais independentemente nesta página e durante o processamento da compra, como determinado na
    Política de Cookies da Xsolla<\/a>.
    Observe que, para esta compra, a Xsolla é a comerciante de registros autorizada e controladora dos dados que você enviar a esta página.
    Se você tiver qualquer dúvida, contate o
    Suporte Gamer Xsolla<\/a>.", + "error.title.default" : "Não foi possível processar o pagamento", + "finance-details-cart-items-checkout-title" : "Compra no jogo", + "finance-details-hrk-equal" : "Equivale {{value}}HRK", + "finance-details-hrk-exchange-rate" : "Taxa de câmbio: 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Imposto sobre venda", + "finance-details-sales-tax-including" : "Imposto de venda incluso", + "finance-details-sales-tax-percent" : "Imposto sobre venda {{percent}}%", + "finance-details-subtotal-details-discount" : "Desconto", + "finance-details-subtotal-details-fee" : "Taxa do sistema de pagamento", + "finance-details-subtotal-details-user-balance" : "Saldo", + "finance-details-subtotal-title" : "Subtotal", + "finance-details-total-title" : "Total", + "finance-details-transaction-details-invoice" : "Transação", + "finance-details-transaction-details-time" : "Data", + "finance-details-vat" : "VAT", + "finance-details-vat-ghana" : "VAT, NHIL, Taxa GETFund e Taxa Covid-19", + "finance-details-vat-ghana-including" : "Incluindo VAT, NHIL, Taxa GETFund e Taxa Covid-19", + "finance-details-vat-including" : "Incluindo VAT", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Incluindo GST", + "finance-details-vat-india-percent" : "GST {{percent}}%", + "finance-details-vat-india-percent-including" : "Incluindo {{percent}}% de GST", + "finance-details-vat-percent" : "VAT {{percent}}%", + "finance-details-vat-percent-including" : "Incluindo {{percent}}% de VAT", + "hello" : "Olá mundo!", + "legal" : "Legal", + "privacy-policy" : "Política de Privacidade", + "refund-policy" : "Política de Reembolso", + "saving-status.error.title" : "Não foi possível salvar o método de pagamento", + "saving-status.processing.description" : "Aguardando o salvamento do método de pagamento...", + "saving-status.processing.title" : "Salvando informações de pagamento", + "saving-status.success.title" : "Método de pagamento salvo", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Conexão segura", + "status.error.title" : "Falha no pagamento", + "status.message.auto-cancellation" : "Conclua a transação com o método de pagamento selecionado. Se você escolher não concluí-la na página de métodos de pagamento, feche esta janela. Sua conta não será cobrada.", + "status.processing.description" : "Aguardando a conclusão do pagamento", + "status.processing.title" : "Pagamento em processamento", + "status.success.description" : "Enviamos seu recibo para {{email}}", + "status.success.title" : "Pagamento bem-sucedido" + } + } +} diff --git a/src/translations/ro.json b/src/translations/ro.json new file mode 100644 index 00000000..e041b1e2 --- /dev/null +++ b/src/translations/ro.json @@ -0,0 +1,50 @@ +{ + "ro" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay este disponibil numai în browserul Safari cu OS 10.12 Sierra sau iOS 10. instalat. Schimbați browserul sau selectați o altă opțiune de plată.", + "apple-pay.errors.payment" : "Nu putem procesa plata ta. Asigură-te că eşti logat în Apple pe dispozitivul tău înainte să încerci din nou. Sincronizarea modificărilor în setările Apple durează câteva minute.", + "cookie-policy" : "Politica privind modulele cookie", + "disclaimer-with-cookies" : "Continuând, confirmați că sunteți adult în conformitate cu legislația națională și sunteți de acord să respectați acordul utilizatorului final Xsolla<\/a>.
    Xsolla colectează în mod independent module cookie esențiale pe această pagină și în continuare în timpul procesării achiziției care este acoperită de
    politica Xsolla privind modulele cookie <\/a>.
    Vă rugăm să rețineți că, pentru această achiziție, Xsolla este comerciantul autorizat de înregistrare și controlor al datelor pe care le trimiteți pe această pagină.
    Dacă aveți întrebări, vă rugăm să vă adresați serviciului
    Xsolla de asistență pentru jucători<\/a>.", + "error.title.default" : "Nu s-a putut procesa plata", + "finance-details-cart-items-checkout-title" : "Achiziție în joc", + "finance-details-hrk-equal" : "Echivalent al sumei de {{value}} HRK", + "finance-details-hrk-exchange-rate" : "Curs de schimb: 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Taxa pe vânzări", + "finance-details-sales-tax-including" : "Taxe pe vânzări incluse", + "finance-details-sales-tax-percent" : "Taxa pe vânzări {{percent}}%", + "finance-details-subtotal-details-discount" : "Reducere", + "finance-details-subtotal-details-fee" : "Taxă sistem de plată", + "finance-details-subtotal-details-user-balance" : "Sold", + "finance-details-subtotal-title" : "Subtotal", + "finance-details-total-title" : "Total", + "finance-details-transaction-details-invoice" : "Tranzacție", + "finance-details-transaction-details-time" : "Dată", + "finance-details-vat" : "TVA", + "finance-details-vat-ghana" : "VAT, NHIL, GETFund Levy și Covid-19 Levy", + "finance-details-vat-ghana-including" : "Inclusiv VAT, NHIL, GETFund Levy și Covid-19 Levy", + "finance-details-vat-including" : "Inclusiv TVA", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Inclusiv GST", + "finance-details-vat-india-percent" : "GST {{percent}}%", + "finance-details-vat-india-percent-including" : "Inclusiv {{percent}}% GST", + "finance-details-vat-percent" : "TVA {{percent}}%", + "finance-details-vat-percent-including" : "Inclusiv {{percent}}% TVA", + "hello" : "Salutare, lume!", + "legal" : "Juridic", + "privacy-policy" : "Politica de confidențialitate", + "refund-policy" : "Politica de retur", + "saving-status.error.title" : "Nu am putut salva metoda de plată", + "saving-status.processing.description" : " Se așteaptă metoda de plată pentru salvare...", + "saving-status.processing.title" : " Se salvează informațiile de plată", + "saving-status.success.title" : "Metodă de plată salvată", + "sctl-indications" : "Indicații SCTL", + "secure-connection" : "Conexiune securizată", + "status.error.title" : "Plată eșuată", + "status.message.auto-cancellation" : "Finalizați tranzacția cu metoda de plată selectată. Dacă alegeți să nu o finalizați pe pagina metodei de plată, închideți această fereastră. Contul dvs. nu va fi debitat.", + "status.processing.description" : "Se aşteaptă încheierea plății...", + "status.processing.title" : "Procesare plată", + "status.success.description" : "Am trimis chitanța dumneavoastră la {{email}}", + "status.success.title" : "Plată realizată cu succes" + } + } +} diff --git a/src/translations/ru.json b/src/translations/ru.json new file mode 100644 index 00000000..d057a177 --- /dev/null +++ b/src/translations/ru.json @@ -0,0 +1,50 @@ +{ + "ru" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Оплата с помощью Apple Pay доступна только в браузере Safari с OS 10.12 Sierra или IOS 10. Воспользуйтесь другим браузером или измените способ оплаты.", + "apple-pay.errors.payment" : "Оплата не выполнена. Перед повторной попыткой убедитесь, что вы вошли в свой аккаунт Apple и у вас настроен кошелек Apple Pay. Сихнронизация с измененными настройками Apple может занять пару минут.", + "cookie-policy" : "Политика использования cookie", + "disclaimer-with-cookies" : "Продолжая оплату, вы подтверждаете, что достигли возраста совершеннолетия в вашей стране и согласны с условиями Пользовательского соглашения Xsolla<\/a>.
    Xsolla независимо собирает необходимые cookie-файлы на этой и других страницах во время обработки платежа, как и указано в
    Политике Cookie компании Xsolla<\/a>.
    Необходимо учитывать, что Xsolla является Получателем платежа и контроллером информации, которую вы предоставляете на этой странице.
    Если у вас есть вопросы, свяжитесь с нашей
    Службой поддержки<\/a>.", + "error.title.default" : "Не получилось обработать платеж", + "finance-details-cart-items-checkout-title" : "Внутриигровая покупка", + "finance-details-hrk-equal" : "Равняется {{value}} HRK", + "finance-details-hrk-exchange-rate" : "Обменный курс: 1 EUR = 7.5345 HRK", + "finance-details-sales-tax" : "Налог с продаж", + "finance-details-sales-tax-including" : "Включая налог с продаж", + "finance-details-sales-tax-percent" : "Налог с продаж {{percent}}%", + "finance-details-subtotal-details-discount" : "Скидка", + "finance-details-subtotal-details-fee" : "Комиссия платежной системы", + "finance-details-subtotal-details-user-balance" : "Баланс", + "finance-details-subtotal-title" : "Сумма", + "finance-details-total-title" : "К оплате", + "finance-details-transaction-details-invoice" : "Транзакция", + "finance-details-transaction-details-time" : "Дата", + "finance-details-vat" : "НДС", + "finance-details-vat-ghana" : "НДС и взносы NHIL, GETFund и Covid-19", + "finance-details-vat-ghana-including" : "Включая НДС и взносы NHIL, GETFund и Covid-19", + "finance-details-vat-including" : "Включая НДС", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Включая GST", + "finance-details-vat-india-percent" : "GST {{percent}}%", + "finance-details-vat-india-percent-including" : "Включая {{percent}}% GST", + "finance-details-vat-percent" : "НДС {{percent}}%", + "finance-details-vat-percent-including" : "Включая НДС {{percent}}%", + "hello" : "Привет, мир!", + "legal" : "Юридические документы", + "privacy-policy" : "Политика конфиденциальности", + "refund-policy" : "Политика возврата", + "saving-status.error.title" : "Не получилось сохранить способ оплаты", + "saving-status.processing.description" : "Подождите, пока сохранится способ оплаты...", + "saving-status.processing.title" : "Сохраняем данные", + "saving-status.success.title" : "Способ оплаты сохранен", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Безопасное соединение", + "status.error.title" : "Платеж не прошел", + "status.message.auto-cancellation" : "Завершите транзакцию с выбранным способом оплаты. Если вы решите не завершать платеж на странице способа оплаты, закройте это окно. Деньги не спишутся.", + "status.processing.description" : "Ожидаем завершения оплаты...", + "status.processing.title" : "Обработка платежа", + "status.success.description" : "Мы отправили чек на {{email}}", + "status.success.title" : "Платеж прошел" + } + } +} diff --git a/src/translations/th.json b/src/translations/th.json new file mode 100644 index 00000000..fd8d1a4a --- /dev/null +++ b/src/translations/th.json @@ -0,0 +1,50 @@ +{ + "th" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "สามารถใช้ Apple Pay บนเบราว์เซอร์ Safari ที่มีการติดตั้ง OS 10.12 Sierra หรือ IOS 10 ไว้เท่านั้น โปรดเปลี่ยนเบราว์เซอร์ของคุณหรือเลือกช่องทางการชำระเงินอื่น", + "apple-pay.errors.payment" : "ไม่สามารถประมวลผลการชำระเงินของคุณ โปรดตรวจสอบให้แน่ใจว่าคุณลงชื่อเข้าใช้ Apple บนอุปกรณ์และตั้งค่า Apple Pay หลังจากนั้นให้ลองอีกครั้ง การเปลี่ยนแปลงการตั้งค่า Apple จะใช้เวลาในการซิงค์ประมาณสองถึงสามนาที", + "cookie-policy" : "นโยบายคุกกี้", + "disclaimer-with-cookies" : "การดำเนินการต่อหมายถึงคุณยืนยันว่าคุณเป็นผู้ใหญ่ตามกฎหมายของรัฐและตกลงที่จะผูกพันตาม ข้อตกลงการอนุญาตสำหรับผู้ใช้ปลายทางของเอ็กซ์โซลล่า <\/a>
    เอ็กซ์โซลล่ารวบรวมคุกกี้ที่จำเป็นอย่างอิสระในหน้านี้ และเพิ่มเติมในขณะที่ทำการประมวลผลการซื้อซึ่งครอบคลุมอยู่ใน
    นโยบายคุกกี้ของเอ็กซ์โซลล่า<\/a>
    โปรดทราบว่าสำหรับการซื้อนี้ทางเอ็กซ์โซลล่าเป็นผู้ค้าที่ได้รับอนุญาตในการบันทึก และผู้ควบคุมข้อมูลที่คุณส่งเข้ามาในหน้านี้
    หากคุณมีคำถามใด ๆ โปรดติดต่อไปยัง
    ฝ่ายสนับสนุนเกมเมอร์ของเอ็กซ์โซล่า<\/a>", + "error.title.default" : "ไม่สามารถดำเนินการชำระเงินได้", + "finance-details-cart-items-checkout-title" : "การซื้อภายในเกม", + "finance-details-hrk-equal" : "เท่ากับ {{value}} HRK", + "finance-details-hrk-exchange-rate" : "อัตราแลกเปลี่ยน: 1 EUR = 7.5345 HRK", + "finance-details-sales-tax" : "ภาษีขาย", + "finance-details-sales-tax-including" : "รวมภาษีการขายแล้ว", + "finance-details-sales-tax-percent" : "ภาษีขาย {{percent}}%", + "finance-details-subtotal-details-discount" : "ส่วนลด", + "finance-details-subtotal-details-fee" : "ค่าธรรมเนียมระบบการชำระเงิน", + "finance-details-subtotal-details-user-balance" : "ยอดคงเหลือ", + "finance-details-subtotal-title" : "ยอดรวมย่อย", + "finance-details-total-title" : "ยอดรวม", + "finance-details-transaction-details-invoice" : "ธุรกรรม", + "finance-details-transaction-details-time" : "วันที่", + "finance-details-vat" : "ภาษีมูลค่าเพิ่ม", + "finance-details-vat-ghana" : "การจัดเก็บภาษี VAT, NHIL, GETFund และ Covid-19", + "finance-details-vat-ghana-including" : "รวมถึง การจัดเก็บภาษี VAT, NHIL, GETFund และ Covid-19", + "finance-details-vat-including" : "รวมภาษีมูลค่าเพิ่ม", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "รวม GST", + "finance-details-vat-india-percent" : "GST {{percent}}%", + "finance-details-vat-india-percent-including" : "รวม GST {{percent}}%", + "finance-details-vat-percent" : "VAT {{percent}}%", + "finance-details-vat-percent-including" : "รวม VAT {{percent}}% ", + "hello" : "สวัสดีชาวโลก!", + "legal" : "ข้อมูลด้านกฎหมาย", + "privacy-policy" : "นโยบายความเป็นส่วนตัว", + "refund-policy" : "นโยบายการคืนเงิน", + "saving-status.error.title" : "ไม่สามารถบันทึกวิธีการชำระเงินได้", + "saving-status.processing.description" : " กำลังรอวิธีการชำระเงินที่จะบันทึก...", + "saving-status.processing.title" : " กำลังบันทึกข้อมูลการชำระเงิน ", + "saving-status.success.title" : " บันทึกวิธีการชำระเงินแล้ว", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "การเชื่อมต่อที่ปลอดภัย", + "status.error.title" : "ชำระเงินไม่สำเร็จ", + "status.message.auto-cancellation" : "ทำธุรกรรมให้เสร็จสิ้นด้วยวิธีการชำระเงินที่เลือก หากคุณเลือกที่จะไม่ทำธุรกรรมในหน้าวิธีการชำระเงิน โปรดปิดหน้าต่างนี้ บัญชีของคุณจะไม่ถูกเรียกเก็บเงิน", + "status.processing.description" : "กำลังรอการชำระเงินให้เสร็จสิ้น...", + "status.processing.title" : "กำลังดำเนินการชำระเงิน", + "status.success.description" : "เราส่งใบเสร็จรับเงินไปยัง {{email}} แล้ว", + "status.success.title" : "ชำระเงินสำเร็จแล้ว" + } + } +} diff --git a/src/translations/tr.json b/src/translations/tr.json new file mode 100644 index 00000000..1b3cdae9 --- /dev/null +++ b/src/translations/tr.json @@ -0,0 +1,50 @@ +{ + "tr" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay sadece 10.12 Sierra veya iOS 10 yüklü cihazlarda Safari tarayıcısında kullanılabilir. Lütfen tarayıcınızı değiştirin veya başka bir ödeme yöntemi seçin.", + "apple-pay.errors.payment" : "Ödemeniz işleme konulamadı. Tekrar denemeden önce cihazınızda Apple'a giriş yapmış olduğunuzdan emin olun ve Apple Pay'i kurun. Apple ayarlarındaki değişikliklerin senkronize olması birkaç dakika sürer.", + "cookie-policy" : "Çerez Politikası", + "disclaimer-with-cookies" : "Devam ederek ilgili eyalet yasalarına göre yetişkin olduiğunuzu ve Xsolla’nın EULA<\/a> belgesine tabi olduğunuzu kabul etmiş olursunuz.
    Xsolla, bağımsız olarak bu sayfadaki başlıca çerezleri toplar. Bunlar,
    Xsolla’nın Çerez Politikasında<\/a> açıklanmıştır.
    Lütfen bu satın alım için Xsolla'nın yetkili hem yetkili satıcı hem de bu sayfaya girdiğiniz verilerin kontrolcüsü olduğunu unutmayın.
    Sorularınız varsa lütfen
    Xsolla Oyuncu Destek Ekibi<\/a> ile iletişime geçin.", + "error.title.default" : "Ödeme işlenemedi", + "finance-details-cart-items-checkout-title" : "Oyun içi satın alma", + "finance-details-hrk-equal" : "{{value}} Hırvat kunasına eşittir", + "finance-details-hrk-exchange-rate" : "Döviz kuru: 1 euro = 7,5345 Hırvat kunası", + "finance-details-sales-tax" : "Satış vergisi", + "finance-details-sales-tax-including" : "Satış vergisi dahildir", + "finance-details-sales-tax-percent" : "Satış vergisi %{{percent}}", + "finance-details-subtotal-details-discount" : "İndirim", + "finance-details-subtotal-details-fee" : "Ödeme sistemi ücreti", + "finance-details-subtotal-details-user-balance" : "Bakiye", + "finance-details-subtotal-title" : "Ara Toplam", + "finance-details-total-title" : "Toplam", + "finance-details-transaction-details-invoice" : "İşlem", + "finance-details-transaction-details-time" : "Tarih", + "finance-details-vat" : "KDV", + "finance-details-vat-ghana" : "KDV, NHIL, GETFund Vergisi ve Covid-19 Vergisi", + "finance-details-vat-ghana-including" : "KDV, NHIL, GETFund Vergisi ve Covid-19 Vergisi dahil", + "finance-details-vat-including" : "KDV dahil", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "GST dahil", + "finance-details-vat-india-percent" : "GST %{{percent}}", + "finance-details-vat-india-percent-including" : "%{{percent}} GST dahildir", + "finance-details-vat-percent" : "KDV %{{percent}}", + "finance-details-vat-percent-including" : "%{{percent}} KDV dahildir", + "hello" : "Merhaba dünya!", + "legal" : "Yasal", + "privacy-policy" : "Gizlilik Politikası", + "refund-policy" : "İade Politikası", + "saving-status.error.title" : "Ödeme yöntemi kaydedilemedi", + "saving-status.processing.description" : "Ödeme yönteminin kaydedilmesi bekleniyor...", + "saving-status.processing.title" : "Ödeme bilgileri kaydediliyor", + "saving-status.success.title" : "Ödeme yöntemi kaydedildi", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Güvenli bağlantı", + "status.error.title" : "Ödeme alınamadı", + "status.message.auto-cancellation" : "Ödemeyi seçili ödeme yöntemiyle tamamlayın. Ödeme yönteminin sayfasında işlemi tamamlamamayı seçerseniz bu pencereyi kapatın. Bu durumda hesabınızdan ücret tahsil edilmez.", + "status.processing.description" : "Ödeme işleminin tamamlanması bekleniyor...", + "status.processing.title" : "Ödeme işlemi gerçekleştiriliyor", + "status.success.description" : "Makbuzunuzu {{email}} adresine gönderdik", + "status.success.title" : "Ödeme alındı" + } + } +} diff --git a/src/translations/vi.json b/src/translations/vi.json new file mode 100644 index 00000000..3ddbb9de --- /dev/null +++ b/src/translations/vi.json @@ -0,0 +1,50 @@ +{ + "vi" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay chỉ có thể được sử dụng trên trình duyệt Safari cài đặt với OS 10.12 Sierra hoặc iOS 10. Vui lòng đổi trình duyệt hoặc chọn phương thức thanh toán khác.", + "apple-pay.errors.payment" : "Không thể xử lý thanh toán. Hãy đảm bảo bạn đã đăng nhập tài khoản Apple trên thiết bị và thiết lập thanh toán Apple Pay trước khi thử lại. Thay đổi trong thiết lập Apple sẽ mất vài phút để đồng bộ.", + "cookie-policy" : "Chính sách cookie", + "disclaimer-with-cookies" : "Bằng việc tiếp tục, bạn xác nhận mình là người trưởng thành theo luật pháp của tiểu bang và đồng ý chịu sự ràng buộc theo Thỏa thuận người dùng cuối (EULA)<\/a> của Xsolla.
    Xsolla thu thập riêng cookie thiết yếu tại trang này và những vị trí khác trong quá trình xử lý giao dịch theo
    Chính sách cookie<\/a> của Xsolla.
    Xin lưu ý, đối với giao dịch này, Xsolla là thương nhập có thẩm quyền trên giấy tờ và là đơn vị kiểm soát đối với dữ liệu bạn gửi trên trang này.
    Nếu có thắc mắc, vui lòng liên hệ bộ phận
    Hỗ trợ người chơi của Xsolla<\/a>.", + "error.title.default" : "Không thể xử lý khoản thanh toán", + "finance-details-cart-items-checkout-title" : "Giao dịch trong trò chơi", + "finance-details-hrk-equal" : "Tương đương {{value}} HRK", + "finance-details-hrk-exchange-rate" : "Tỷ giá: 1 EUR = 7,5345 HRK", + "finance-details-sales-tax" : "Thuế doanh thu", + "finance-details-sales-tax-including" : "Đã bao gồm thuế doanh thu", + "finance-details-sales-tax-percent" : "Thuế doanh thu {{percent}}%", + "finance-details-subtotal-details-discount" : "Giảm giá", + "finance-details-subtotal-details-fee" : "Phí hệ thống thanh toán", + "finance-details-subtotal-details-user-balance" : "Số dư", + "finance-details-subtotal-title" : "Tạm tính", + "finance-details-total-title" : "Tổng", + "finance-details-transaction-details-invoice" : "Giao dịch", + "finance-details-transaction-details-time" : "Ngày", + "finance-details-vat" : "Thuế GTGT", + "finance-details-vat-ghana" : "Thuế Giá trị gia tăng (VAT), Thuế phụ thu bảo hiểm y tế quốc gia (NHIL), Thuế phụ thu GETFund và Thuế phụ thu Covid-19", + "finance-details-vat-ghana-including" : "Bao gồm thuế Giá trị gia tăng (VAT), Thuế phụ thu bảo hiểm y tế quốc gia (NHIL), Thuế phụ thu GETFund và Thuế phụ thu Covid-19", + "finance-details-vat-including" : "Bao gồm thuế GTGT", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "Bao gồm thuế hàng hóa dịch vụ (GST)", + "finance-details-vat-india-percent" : "Thuế hàng hóa và dịch vụ (GST) {{percent}}%", + "finance-details-vat-india-percent-including" : "Bao gồm {{percent}}% thuế hàng hóa và dịch vụ (GST)", + "finance-details-vat-percent" : "Thuế giá trị gia tăng (VAT) {{percent}}%", + "finance-details-vat-percent-including" : "Bao gồm {{percent}}% thuế GTGT", + "hello" : "Xin chào thế giới!", + "legal" : "Pháp lý", + "privacy-policy" : "Chính sách Quyền riêng tư", + "refund-policy" : "Chính sách hoàn tiền", + "saving-status.error.title" : "Không thể lưu phương thức thanh toán", + "saving-status.processing.description" : "Đang chờ lưu phương thức thanh toán...", + "saving-status.processing.title" : "Đang lưu thông tin thanh toán", + "saving-status.success.title" : "Đã lưu phương thức thanh toán", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "Kết nối bảo mật", + "status.error.title" : "Thanh toán thất bại", + "status.message.auto-cancellation" : "Hoàn tất giao dịch với phương thức thanh toán đã chọn. Nếu bạn không muốn hoàn tất giao dịch trên trang của phương thức thanh toán, hãy đóng cửa sổ này lại. Tài khoản của bạn sẽ không bị tính phí.", + "status.processing.description" : "Đợi thanh toán hoàn tất...", + "status.processing.title" : "Đang xử lý thanh toán", + "status.success.description" : "Biên nhận đã được gửi đến {{email}}", + "status.success.title" : "Thanh toán thành công" + } + } +} diff --git a/src/translations/zh_HANS.json b/src/translations/zh_HANS.json new file mode 100644 index 00000000..83443c4e --- /dev/null +++ b/src/translations/zh_HANS.json @@ -0,0 +1,50 @@ +{ + "zh_HANS" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay只能在OS 10.12 Sierra或IOS 10上的Safari浏览器中使用。请更换浏览器或选择其他支付方式。", + "apple-pay.errors.payment" : "无法处理您的支付。请确保已在您的设备上登录Apple并已设置Apple Pay,然后重试。Apple设置的更改可能需要几分钟才能同步。", + "cookie-policy" : "Cookie政策", + "disclaimer-with-cookies" : "继续即表示您确认根据您所在地的法律,您是成年人,并同意受艾克索拉的EULA<\/a>所约束。
    艾克索拉按照
    艾克索拉的Cookie政策<\/a>在处理购买时在本页面及后续页面独立收集必要的Cookie。
    请注意,对于此次购买,艾克索拉是您在本页面提交的数据的授权记录商户和控制方。
    如有任何问题,请联系
    艾克索拉玩家支持<\/a>。", + "error.title.default" : "无法处理支付", + "finance-details-cart-items-checkout-title" : "游戏内购买", + "finance-details-hrk-equal" : "等于 {{value}} HRK", + "finance-details-hrk-exchange-rate" : "汇率:1 欧元 = 7.5345 HRK", + "finance-details-sales-tax" : "销售税", + "finance-details-sales-tax-including" : "含销售税", + "finance-details-sales-tax-percent" : "销售税 {{percent}}%", + "finance-details-subtotal-details-discount" : "折扣", + "finance-details-subtotal-details-fee" : "支付系统使用费", + "finance-details-subtotal-details-user-balance" : "余额", + "finance-details-subtotal-title" : "小计", + "finance-details-total-title" : "总计", + "finance-details-transaction-details-invoice" : "交易", + "finance-details-transaction-details-time" : "日期", + "finance-details-vat" : "增值税", + "finance-details-vat-ghana" : "VAT、NHIL、GETFund征税和Covid-19征税", + "finance-details-vat-ghana-including" : "含VAT、NHIL、GETFund征税和Covid-19征税", + "finance-details-vat-including" : "含增值税", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "包含GST", + "finance-details-vat-india-percent" : "商品和服务税 {{percent}}%", + "finance-details-vat-india-percent-including" : "含{{percent}}%商品和服务税", + "finance-details-vat-percent" : "增值税 {{percent}}%", + "finance-details-vat-percent-including" : "含{{percent}}%增值税", + "hello" : "你好,世界!", + "legal" : "法律信息", + "privacy-policy" : "隐私政策", + "refund-policy" : "退款政策", + "saving-status.error.title" : "无法保存支付方式", + "saving-status.processing.description" : "正在保存支付方式,请稍等...", + "saving-status.processing.title" : "正在保存支付信息", + "saving-status.success.title" : "已保存支付方式", + "sctl-indications" : "SCTL Indications", + "secure-connection" : "安全连接", + "status.error.title" : "支付失败", + "status.message.auto-cancellation" : "使用所选支付方式完成交易。如选择不在支付方式页面完成交易,请关闭此窗口。您的帐户将不会被扣款。", + "status.processing.description" : "正在等待支付完成...", + "status.processing.title" : "正在处理付款", + "status.success.description" : "您的收据已发送至{{email}}", + "status.success.title" : "支付成功" + } + } +} diff --git a/src/translations/zh_HANT.json b/src/translations/zh_HANT.json new file mode 100644 index 00000000..d954fa35 --- /dev/null +++ b/src/translations/zh_HANT.json @@ -0,0 +1,50 @@ +{ + "zh_HANT" : { + "translation" : { + "apple-pay.errors.device-is-not-capable" : "Apple Pay 僅適用於已安裝 OS 10.12 Sierra 或 IOS 10 的 Safari 瀏覽器。請變更您的瀏覽器或選取其他付款選項。", + "apple-pay.errors.payment" : "無法為您處理付款。請確認有透過裝置,登入 Apple,並在重試之前,設定好 Apple Pay。凡 Apple 設定變更,大概都要花幾分鐘時間,才能完成同步處理。", + "cookie-policy" : "Cookie 政策", + "disclaimer-with-cookies" : "依各州法律規定,繼續使用,即表示您確認自己已成年,並同意接受愛索拉之使用者授權合約<\/a>約束。
    愛索拉會個別收集網頁上的基本 Cookie,進一步處理購買項目,這些在
    愛索拉官方 Cookie 政策<\/a>裡都有涵蓋。
    請注意關於這個購買項目,愛索拉商家已取得授權,也是資料控制者,您可在網頁送出資料。
    如有任何問題,請直接找
    愛索拉玩家支援<\/a>洽詢。", + "error.title.default" : "無法處理付款", + "finance-details-cart-items-checkout-title" : "遊戲內選購", + "finance-details-hrk-equal" : "相當於 {{value}} 克羅埃西亞庫納(HRK)", + "finance-details-hrk-exchange-rate" : "匯率換算:1 歐元 (EUR) = 7.5345 克羅埃西亞庫納 (HRK)", + "finance-details-sales-tax" : "營業稅", + "finance-details-sales-tax-including" : "含營業稅", + "finance-details-sales-tax-percent" : "營業稅 {{percent}}%", + "finance-details-subtotal-details-discount" : "折扣", + "finance-details-subtotal-details-fee" : "支付系統費用", + "finance-details-subtotal-details-user-balance" : "餘額", + "finance-details-subtotal-title" : "小計", + "finance-details-total-title" : "總計", + "finance-details-transaction-details-invoice" : "交易", + "finance-details-transaction-details-time" : "日期", + "finance-details-vat" : "加值稅", + "finance-details-vat-ghana" : "含增值稅,徵收國民健康保險、迦納教育信託基金和 Covid-19 等稅金", + "finance-details-vat-ghana-including" : "含增值稅,徵收國民健康保險、迦納教育信託基金和 Covid-19 等稅金", + "finance-details-vat-including" : "含加值稅", + "finance-details-vat-india" : "GST", + "finance-details-vat-india-including" : "含商品及服務稅 (GST)", + "finance-details-vat-india-percent" : "商品及服務稅 {{percent}}%", + "finance-details-vat-india-percent-including" : "含{{percent}}%商品及服務稅", + "finance-details-vat-percent" : "加值稅 {{percent}}%", + "finance-details-vat-percent-including" : "含{{percent}}% 增值稅", + "hello" : "你好,世界!", + "legal" : "法律文件", + "privacy-policy" : "隱私權政策", + "refund-policy" : "退款政策", + "saving-status.error.title" : "無法儲存付款方式", + "saving-status.processing.description" : "正在等待儲存付款方式...", + "saving-status.processing.title" : "正在儲存付款資訊", + "saving-status.success.title" : "付款方式已儲存", + "sctl-indications" : "特定商業交易法聲明", + "secure-connection" : "安全連線", + "status.error.title" : "付款失敗", + "status.message.auto-cancellation" : "請使用所選的付款方式完成交易。如果您選擇不在付款方式的頁面上完成交易,請關閉此視窗,您的帳戶將不會被扣費。", + "status.processing.description" : "等待完成付款......", + "status.processing.title" : "正在處理付款", + "status.success.description" : "我們已將您的收據發送至 {{email}}", + "status.success.title" : "付款成功" + } + } +} diff --git a/src/web-components.ts b/src/web-components.ts new file mode 100644 index 00000000..5174fc15 --- /dev/null +++ b/src/web-components.ts @@ -0,0 +1,43 @@ +import { SubmitButtonComponent } from './features/headless-checkout/web-components/submit-button/submit-button.component'; +import { TextComponent } from './features/headless-checkout/web-components/text-component/text.component'; +import { PaymentMethodsComponent } from './features/headless-checkout/web-components/payment-methods/payment-methods.component'; +import { LegalComponent } from './features/headless-checkout/web-components/legal/legal.component'; +import { StatusComponent } from './features/headless-checkout/web-components/status/status.component'; +import { FinanceDetailsComponent } from './features/headless-checkout/web-components/finance-details/finance-details.component'; +import { PriceTextComponent } from './features/headless-checkout/web-components/finance-details/price-text/price-text.component'; +import { CardNumberComponent } from './features/headless-checkout/web-components/card-number/card-number.component'; +import { ThreeDsComponent } from './features/headless-checkout/web-components/three-ds/three-ds.component'; +import { PhoneComponent } from './features/headless-checkout/web-components/phone-component/phone.component'; +import { SelectComponent } from './features/headless-checkout/web-components/select/select.component'; +import { CheckboxComponent } from './features/headless-checkout/web-components/checkbox/checkbox.component'; +import { UserBalanceComponent } from './features/headless-checkout/web-components/user-balance-component/user-balance-component'; +import { PaymentFormMessagesComponent } from './features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component'; +import { GooglePayButtonComponent } from './features/headless-checkout/web-components/pages/google-pay/google-pay-button.component'; +import { QrCodeComponent } from './features/headless-checkout/web-components/qr-code/qr-code.component'; +import { ApplePayComponent } from './features/headless-checkout/web-components/apple-pay/apple-pay.component'; +import { PaymentFormComponent } from './features/headless-checkout/web-components/payment-form/payment-form.component'; +import { SavedMethodsComponent } from './features/headless-checkout/web-components/saved-methods/saved-methods.component'; +import { TotalComponent } from './features/headless-checkout/web-components/finance-details/total.component'; + +export { + SubmitButtonComponent, + TextComponent, + CardNumberComponent, + PaymentMethodsComponent, + PriceTextComponent, + FinanceDetailsComponent, + LegalComponent, + StatusComponent, + ThreeDsComponent, + PhoneComponent, + SelectComponent, + CheckboxComponent, + UserBalanceComponent, + PaymentFormMessagesComponent, + GooglePayButtonComponent, + QrCodeComponent, + ApplePayComponent, + PaymentFormComponent, + SavedMethodsComponent, + TotalComponent, +}; diff --git a/tsconfig.json b/tsconfig.json index 2d01b57f..f3545a1e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compileOnSave": false, "compilerOptions": { "baseUrl": "./", - "outDir": "./lib", + "outDir": "./dist", "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitReturns": true, @@ -11,16 +11,15 @@ "declaration": true, "downlevelIteration": true, "experimentalDecorators": true, + "emitDecoratorMetadata": true, "moduleResolution": "node", "importHelpers": true, "target": "es2015", "module": "es2020", "lib": ["es2018", "dom", "dom.iterable"], "resolveJsonModule": true, - "esModuleInterop": true, - "allowJs": true + "esModuleInterop": true }, "allowSyntheticDefaultImports": true, - "exclude": ["node_modules"], - "include": ["src/**/*"] + "exclude": ["node_modules"] } diff --git a/webpack.config.js b/webpack.config.js index 61365a82..98145fa4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,11 +12,19 @@ const config = { exclude: /node_modules/, }, { - test: /\.s[ac]ss$/i, + test: /\.string-style.s[ac]ss$/i, + use: ['raw-loader', 'sass-loader'], + }, + { + test: /(?